@avalabs/fusion-sdk 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/transfer-service/avalanche-evm/_handlers/track-transfer.cjs +1 -1
- package/dist/transfer-service/avalanche-evm/_handlers/track-transfer.cjs.map +1 -1
- package/dist/transfer-service/avalanche-evm/_handlers/track-transfer.js +1 -1
- package/dist/transfer-service/avalanche-evm/_handlers/track-transfer.js.map +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.cjs +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.cjs.map +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.js +1 -1
- package/dist/transfer-service/markr/_handlers/estimate-native-fee.js.map +1 -1
- package/dist/transfer-service/markr/_handlers/transfer-asset.cjs +1 -1
- package/dist/transfer-service/markr/_handlers/transfer-asset.cjs.map +1 -1
- package/dist/transfer-service/markr/_handlers/transfer-asset.js +1 -1
- package/dist/transfer-service/markr/_handlers/transfer-asset.js.map +1 -1
- package/dist/transfer-service/markr/_solana-utils.cjs +2 -0
- package/dist/transfer-service/markr/_solana-utils.cjs.map +1 -0
- package/dist/transfer-service/markr/_solana-utils.js +2 -0
- package/dist/transfer-service/markr/_solana-utils.js.map +1 -0
- package/package.json +3 -3
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../errors.cjs`),t=require(`../../_utils.cjs`),n=require(`../_utils/transfer-data.cjs`);let r=require(`viem`);function
|
|
1
|
+
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../errors.cjs`),t=require(`../../_utils.cjs`),n=require(`../_utils/transfer-data.cjs`);let r=require(`viem`);const i=1024n;function a({config:n}){return({transfer:i,updateListener:a})=>{let c=new AbortController,l=()=>{c.abort()};if(i.status===`completed`||i.status===`failed`)return{cancel:l,result:Promise.resolve(i)};let{txHash:u}=i.source;if(!(0,r.isHash)(u)){let t={...i,errorCode:e.ErrorCode.INVALID_PARAMS,errorReason:`Invalid source transaction hash`,failedAtMs:Date.now(),status:`failed`};return a(t),{cancel:l,result:Promise.resolve(t)}}let f=t.getEvmClientForChain({chain:i.sourceChain}),p=t.getEvmClientForChain({chain:i.targetChain}),m=structuredClone(i);return{cancel:l,result:(async()=>{for(;!c.signal.aborted;){let e=!1;switch(m.status){case`source-pending`:m=await o({currentTransfer:m,signal:c.signal,sourceClient:f}),a(m);break;case`target-pending`:m=await s({assetIn:m.sourceAsset,currentTransfer:m,evmConfig:n,signal:c.signal,sourceChainId:m.sourceChain.chainId,targetChainId:m.targetChain.chainId,targetClient:p}),e=m.status===`target-pending`&&!m.target?.txHash,a(m),e&&await d({signal:c.signal});break;default:return m}}return m})()}}}async function o({currentTransfer:n,signal:i,sourceClient:a}){let{confirmationCount:o,requiredConfirmationCount:s,txHash:c}=n.source;if(!(0,r.isHash)(c))return{...n,errorCode:e.ErrorCode.INVALID_PARAMS,errorReason:`Invalid source transaction hash`,failedAtMs:Date.now(),status:`failed`};if(o>=s)return{...n,status:`target-pending`,target:{confirmationCount:0,requiredConfirmationCount:1,startedAtMs:Date.now()}};try{let r=await t.awaitOrAbort(a.waitForTransactionReceipt({confirmations:Math.min(o+1,s),hash:c}),i);if(r.status===`aborted`)return n;let l=r.value;if(l.status===`reverted`)return{...n,errorCode:e.ErrorCode.TRANSACTION_REVERTED,errorReason:`Source transaction was reverted`,failedAtMs:Date.now(),status:`failed`};let u=await a.getTransactionConfirmations({transactionReceipt:l}).then(e=>Number(e)).catch(()=>o+1);return u<s?{...n,source:{...n.source,confirmationCount:u}}:{...n,source:{...n.source,confirmationCount:u},status:`target-pending`,target:{confirmationCount:0,requiredConfirmationCount:1,startedAtMs:Date.now()}}}catch{return n}}async function s({assetIn:i,currentTransfer:a,evmConfig:o,signal:s,sourceChainId:u,targetChainId:d,targetClient:f}){let p=a.target??{confirmationCount:0,requiredConfirmationCount:1,startedAtMs:Date.now()};if(p.confirmationCount>=p.requiredConfirmationCount)return{...a,status:`completed`,completedAtMs:Date.now()};if(!p.txHash){let{ethToAva:e,target:{token:{address:t}}}=n.getTransferData({assetIn:i,sourceChainId:u,targetChainId:d},o),r=typeof f.getBlockNumber==`function`?await f.getBlockNumber().catch(()=>void 0):void 0,s=await(e?c({contractAddress:t,latestBlockNumber:r,sourceTxHash:a.source.txHash,targetClient:f,targetStartBlockNumber:a.source.targetStartBlockNumber}):l({contractAddress:t,evmConfig:o,fromAddress:a.fromAddress,latestBlockNumber:r,targetClient:f,targetStartBlockNumber:a.source.targetStartBlockNumber})).catch(()=>null);return s?{...a,target:{...p,txHash:s}}:r===void 0?a:{...a,source:{...a.source,targetStartBlockNumber:r}}}if(!(0,r.isHash)(p.txHash))return{...a,errorCode:e.ErrorCode.INVALID_PARAMS,errorReason:`Invalid target transaction hash`,failedAtMs:Date.now(),status:`failed`};try{let n=await t.awaitOrAbort(f.waitForTransactionReceipt({confirmations:p.requiredConfirmationCount,hash:p.txHash}),s);if(n.status===`aborted`)return a;let r=n.value;return r.status===`reverted`?{...a,errorCode:e.ErrorCode.TRANSACTION_REVERTED,errorReason:`Target transaction was reverted`,failedAtMs:Date.now(),status:`failed`}:{...a,completedAtMs:Date.now(),status:`completed`,target:{...p,confirmationCount:await f.getTransactionConfirmations({transactionReceipt:r}).then(e=>Number(e)).catch(()=>p.requiredConfirmationCount)}}}catch{return a}}async function c({contractAddress:e,latestBlockNumber:t,sourceTxHash:n,targetClient:r,targetStartBlockNumber:i}){if(i===void 0)return null;let{fromBlock:a,toBlock:o}=u({latestBlockNumber:t,targetStartBlockNumber:i}),s=(await r.getLogs({address:e,event:{inputs:[{indexed:!1,name:`to`,type:`address`},{indexed:!1,name:`amount`,type:`uint256`},{indexed:!1,name:`feeAddress`,type:`address`},{indexed:!1,name:`feeAmount`,type:`uint256`},{indexed:!1,name:`originTxId`,type:`bytes32`}],name:`Mint`,type:`event`},fromBlock:a,toBlock:o})).find(e=>e.args.originTxId===n);return s?s.transactionHash:null}async function l({contractAddress:e,evmConfig:t,fromAddress:n,latestBlockNumber:i,targetClient:a,targetStartBlockNumber:o}){if(o===void 0||!(0,r.isAddress)(n))return null;let{fromBlock:s,toBlock:c}=u({latestBlockNumber:i,targetStartBlockNumber:o}),l=(await a.getLogs({address:e,args:{dst:n,src:t.walletAddresses.ethereum},event:{inputs:[{indexed:!0,name:`src`,type:`address`},{indexed:!0,name:`dst`,type:`address`},{indexed:!1,name:`wad`,type:`uint256`}],name:`Transfer`,type:`event`},fromBlock:s,toBlock:c})).at(-1);return l?l.transactionHash:null}function u({latestBlockNumber:e,targetStartBlockNumber:t}){let n=t>i?t-i:`earliest`,r=t+i;return{fromBlock:n,toBlock:e!==void 0&&r<e?r:`latest`}}async function d({signal:e}){await t.awaitOrAbort(new Promise(e=>setTimeout(e,15e3)),e)}exports.trackTransferFactory=a;
|
|
2
2
|
//# sourceMappingURL=track-transfer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-transfer.cjs","names":["ErrorCode","getEvmClientForChain","awaitOrAbort","getTransferData"],"sources":["../../../../src/transfer-service/avalanche-evm/_handlers/track-transfer.ts"],"sourcesContent":["import { isAddress, isHash, type Hash } from 'viem';\nimport { ErrorCode } from '../../../errors';\nimport type { Asset } from '../../../types/asset';\nimport type { Caip2ChainId } from '../../../types/caip';\nimport type {\n CompletedTransfer,\n FailedTransfer,\n SourcePendingTransfer,\n TargetPendingTransfer,\n Transfer,\n} from '../../../types/transfer';\nimport type { TransferService } from '../../../types/service';\nimport { awaitOrAbort, getEvmClientForChain } from '../../_utils';\nimport type { EvmConfig } from '../_types';\nimport { getTransferData } from '../_utils/transfer-data';\n\nexport interface TrackTransferFactoryConfig {\n config: EvmConfig;\n}\n\nexport function trackTransferFactory({\n config: evmConfig,\n}: TrackTransferFactoryConfig): TransferService['trackTransfer'] {\n return ({ transfer, updateListener }) => {\n const ac = new AbortController();\n const cancel = () => {\n ac.abort();\n };\n\n // Failed and completed transfers are terminal states.\n if (transfer.status === 'completed' || transfer.status === 'failed') {\n return {\n cancel,\n result: Promise.resolve(transfer),\n };\n }\n\n const { txHash } = transfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...transfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n updateListener(failedTransfer);\n\n return {\n cancel,\n result: Promise.resolve(failedTransfer),\n };\n }\n\n const sourceClient = getEvmClientForChain({ chain: transfer.sourceChain });\n const targetClient = getEvmClientForChain({ chain: transfer.targetChain });\n let currentTransfer: Transfer = structuredClone(transfer);\n\n const executeTracking = async (): Promise<Transfer> => {\n // Create a loop to poll for confirmations and update the transfer status until\n // failed or completed.\n while (!ac.signal.aborted) {\n switch (currentTransfer.status) {\n case 'source-pending': {\n currentTransfer = await _trackSourceTx({\n currentTransfer,\n signal: ac.signal,\n sourceClient,\n });\n updateListener(currentTransfer);\n break;\n }\n case 'target-pending': {\n currentTransfer = await _trackTargetTx({\n assetIn: currentTransfer.sourceAsset,\n currentTransfer,\n evmConfig,\n signal: ac.signal,\n sourceChainId: currentTransfer.sourceChain.chainId,\n targetChainId: currentTransfer.targetChain.chainId,\n targetClient,\n });\n updateListener(currentTransfer);\n break;\n }\n default: {\n return currentTransfer;\n }\n }\n }\n\n return currentTransfer;\n };\n\n return {\n cancel,\n result: executeTracking(),\n };\n };\n}\n\nexport interface TrackBaseParams {\n currentTransfer: Transfer;\n signal: AbortSignal;\n}\n\nexport interface TrackSourceTxParams extends TrackBaseParams {\n currentTransfer: SourcePendingTransfer;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport interface TrackTargetTxParams extends TrackBaseParams {\n assetIn: Asset;\n currentTransfer: TargetPendingTransfer;\n evmConfig: EvmConfig;\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport async function _trackSourceTx({\n currentTransfer,\n signal,\n sourceClient,\n}: TrackSourceTxParams): Promise<Transfer> {\n const { confirmationCount, requiredConfirmationCount, txHash } = currentTransfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n return failedTransfer;\n }\n\n if (confirmationCount >= requiredConfirmationCount) {\n // Move to target-pending\n\n const updatedTransfer: TargetPendingTransfer = {\n ...currentTransfer,\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n };\n\n return updatedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n sourceClient.waitForTransactionReceipt({\n confirmations: Math.min(confirmationCount + 1, requiredConfirmationCount),\n hash: txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Source transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n // Either update confirmation count or move to target-pending\n const newConfirmationCount: number = await sourceClient\n .getTransactionConfirmations({\n // hash: txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => confirmationCount + 1);\n\n // If still not enough confirmations, just update count\n if (newConfirmationCount < requiredConfirmationCount) {\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n } satisfies SourcePendingTransfer;\n }\n\n // Move to target-pending\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n } satisfies TargetPendingTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _trackTargetTx({\n assetIn,\n currentTransfer,\n evmConfig,\n signal,\n sourceChainId,\n targetChainId,\n targetClient,\n}: TrackTargetTxParams): Promise<Transfer> {\n const target = currentTransfer.target ?? {\n confirmationCount: 0,\n requiredConfirmationCount: 1,\n startedAtMs: Date.now(),\n };\n\n if (target.confirmationCount >= target.requiredConfirmationCount) {\n // Move to completed\n const completedTransfer: Transfer = {\n ...currentTransfer,\n status: 'completed',\n completedAtMs: Date.now(),\n };\n return completedTransfer;\n }\n\n if (!target.txHash) {\n // Find the target tx hash via logs and then update the transfer.\n const {\n ethToAva,\n target: {\n token: { address: contractAddress },\n },\n } = getTransferData(\n {\n assetIn,\n sourceChainId,\n targetChainId,\n },\n evmConfig,\n );\n\n const targetTxHash = ethToAva\n ? await _getTargetTxHashEthToAva({\n contractAddress,\n sourceTxHash: currentTransfer.source.txHash,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n })\n : await _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress: currentTransfer.fromAddress,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n });\n\n if (!targetTxHash) {\n return currentTransfer;\n }\n\n return {\n ...currentTransfer,\n target: {\n ...target,\n txHash: targetTxHash,\n },\n } satisfies TargetPendingTransfer;\n }\n\n if (!isHash(target.txHash)) {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid target transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n targetClient.waitForTransactionReceipt({\n // Wait for all required confirmations (which should just be 1 on target).\n // This is a safety in case it's ever more than 1. We just won't \"poll\" multiple times\n // like we do for the source.\n confirmations: target.requiredConfirmationCount,\n hash: target.txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Target transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n return {\n ...currentTransfer,\n completedAtMs: Date.now(),\n status: 'completed',\n target: {\n ...target,\n confirmationCount: await targetClient\n .getTransactionConfirmations({\n // hash: target.txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => target.requiredConfirmationCount),\n },\n } satisfies CompletedTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _getTargetTxHashEthToAva({\n contractAddress,\n sourceTxHash,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n sourceTxHash: string;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n event: {\n inputs: [\n {\n indexed: false,\n name: 'to',\n type: 'address',\n },\n {\n indexed: false,\n name: 'amount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'feeAddress',\n type: 'address',\n },\n {\n indexed: false,\n name: 'feeAmount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'originTxId',\n type: 'bytes32',\n },\n ],\n name: 'Mint',\n type: 'event',\n },\n fromBlock: targetStartBlockNumber,\n toBlock: 'latest', // TODO: Consider limiting the range?\n });\n\n // OriginTxId is not indexed. So it cannot be part of the filter.\n const foundLog = logs.find((log) => log.args.originTxId === sourceTxHash); // TODO: Is my assumption correct here? ie that we want to compare the sourceTxHash?\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n\nexport async function _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n evmConfig: EvmConfig;\n fromAddress: string;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n if (!isAddress(fromAddress)) {\n return null;\n }\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n args: {\n dst: fromAddress,\n src: evmConfig.walletAddresses.ethereum,\n },\n event: {\n inputs: [\n {\n indexed: true,\n name: 'src',\n type: 'address',\n },\n {\n indexed: true,\n name: 'dst',\n type: 'address',\n },\n {\n indexed: false,\n name: 'wad',\n type: 'uint256',\n },\n ],\n name: 'Transfer',\n type: 'event',\n },\n fromBlock: targetStartBlockNumber,\n toBlock: 'latest', // TODO: Consider limiting the range?\n });\n\n const foundLog = logs.at(0); // TODO: Is this correct? Original code was using -1. But we want \"oldest\" log right? ie the closest to the \"fromBlock\"?\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"track-transfer.cjs","names":["ErrorCode","getEvmClientForChain","awaitOrAbort","getTransferData"],"sources":["../../../../src/transfer-service/avalanche-evm/_handlers/track-transfer.ts"],"sourcesContent":["import { isAddress, isHash, type Hash } from 'viem';\nimport { ErrorCode } from '../../../errors';\nimport type { Asset } from '../../../types/asset';\nimport type { Caip2ChainId } from '../../../types/caip';\nimport type {\n CompletedTransfer,\n FailedTransfer,\n SourcePendingTransfer,\n TargetPendingTransfer,\n Transfer,\n} from '../../../types/transfer';\nimport type { TransferService } from '../../../types/service';\nimport { awaitOrAbort, getEvmClientForChain } from '../../_utils';\nimport type { EvmConfig } from '../_types';\nimport { getTransferData } from '../_utils/transfer-data';\n\nconst MAX_LOG_BLOCK_RANGE = 1024n;\nconst TARGET_HASH_POLL_INTERVAL_MS = 15_000;\n\ntype LogSearchRange = {\n fromBlock: bigint | 'earliest';\n toBlock: bigint | 'latest';\n};\n\nexport interface TrackTransferFactoryConfig {\n config: EvmConfig;\n}\n\nexport function trackTransferFactory({\n config: evmConfig,\n}: TrackTransferFactoryConfig): TransferService['trackTransfer'] {\n return ({ transfer, updateListener }) => {\n const ac = new AbortController();\n const cancel = () => {\n ac.abort();\n };\n\n // Failed and completed transfers are terminal states.\n if (transfer.status === 'completed' || transfer.status === 'failed') {\n return {\n cancel,\n result: Promise.resolve(transfer),\n };\n }\n\n const { txHash } = transfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...transfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n updateListener(failedTransfer);\n\n return {\n cancel,\n result: Promise.resolve(failedTransfer),\n };\n }\n\n const sourceClient = getEvmClientForChain({ chain: transfer.sourceChain });\n const targetClient = getEvmClientForChain({ chain: transfer.targetChain });\n let currentTransfer: Transfer = structuredClone(transfer);\n\n const executeTracking = async (): Promise<Transfer> => {\n // Create a loop to poll for confirmations and update the transfer status until\n // failed or completed.\n while (!ac.signal.aborted) {\n let shouldDelayAfterUpdate = false;\n\n switch (currentTransfer.status) {\n case 'source-pending': {\n currentTransfer = await _trackSourceTx({\n currentTransfer,\n signal: ac.signal,\n sourceClient,\n });\n updateListener(currentTransfer);\n break;\n }\n case 'target-pending': {\n currentTransfer = await _trackTargetTx({\n assetIn: currentTransfer.sourceAsset,\n currentTransfer,\n evmConfig,\n signal: ac.signal,\n sourceChainId: currentTransfer.sourceChain.chainId,\n targetChainId: currentTransfer.targetChain.chainId,\n targetClient,\n });\n shouldDelayAfterUpdate = currentTransfer.status === 'target-pending' && !currentTransfer.target?.txHash;\n updateListener(currentTransfer);\n\n if (shouldDelayAfterUpdate) {\n await _waitForNextTargetHashPollingCycle({ signal: ac.signal });\n }\n\n break;\n }\n default: {\n return currentTransfer;\n }\n }\n }\n\n return currentTransfer;\n };\n\n return {\n cancel,\n result: executeTracking(),\n };\n };\n}\n\nexport interface TrackBaseParams {\n currentTransfer: Transfer;\n signal: AbortSignal;\n}\n\nexport interface TrackSourceTxParams extends TrackBaseParams {\n currentTransfer: SourcePendingTransfer;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport interface TrackTargetTxParams extends TrackBaseParams {\n assetIn: Asset;\n currentTransfer: TargetPendingTransfer;\n evmConfig: EvmConfig;\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport async function _trackSourceTx({\n currentTransfer,\n signal,\n sourceClient,\n}: TrackSourceTxParams): Promise<Transfer> {\n const { confirmationCount, requiredConfirmationCount, txHash } = currentTransfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n return failedTransfer;\n }\n\n if (confirmationCount >= requiredConfirmationCount) {\n // Move to target-pending\n\n const updatedTransfer: TargetPendingTransfer = {\n ...currentTransfer,\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n };\n\n return updatedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n sourceClient.waitForTransactionReceipt({\n confirmations: Math.min(confirmationCount + 1, requiredConfirmationCount),\n hash: txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Source transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n // Either update confirmation count or move to target-pending\n const newConfirmationCount: number = await sourceClient\n .getTransactionConfirmations({\n // hash: txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => confirmationCount + 1);\n\n // If still not enough confirmations, just update count\n if (newConfirmationCount < requiredConfirmationCount) {\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n } satisfies SourcePendingTransfer;\n }\n\n // Move to target-pending\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n } satisfies TargetPendingTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _trackTargetTx({\n assetIn,\n currentTransfer,\n evmConfig,\n signal,\n sourceChainId,\n targetChainId,\n targetClient,\n}: TrackTargetTxParams): Promise<Transfer> {\n const target = currentTransfer.target ?? {\n confirmationCount: 0,\n requiredConfirmationCount: 1,\n startedAtMs: Date.now(),\n };\n\n if (target.confirmationCount >= target.requiredConfirmationCount) {\n // Move to completed\n const completedTransfer: Transfer = {\n ...currentTransfer,\n status: 'completed',\n completedAtMs: Date.now(),\n };\n return completedTransfer;\n }\n\n if (!target.txHash) {\n // Find the target tx hash via logs and then update the transfer.\n const {\n ethToAva,\n target: {\n token: { address: contractAddress },\n },\n } = getTransferData(\n {\n assetIn,\n sourceChainId,\n targetChainId,\n },\n evmConfig,\n );\n\n const latestBlockNumber =\n typeof targetClient.getBlockNumber === 'function'\n ? await targetClient.getBlockNumber().catch(() => undefined)\n : undefined;\n\n const targetTxHash = await (ethToAva\n ? _getTargetTxHashEthToAva({\n contractAddress,\n latestBlockNumber,\n sourceTxHash: currentTransfer.source.txHash,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n })\n : _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress: currentTransfer.fromAddress,\n latestBlockNumber,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n })\n ).catch(() => null);\n\n if (!targetTxHash) {\n if (latestBlockNumber === undefined) {\n return currentTransfer;\n }\n\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n targetStartBlockNumber: latestBlockNumber,\n },\n } satisfies TargetPendingTransfer;\n }\n\n return {\n ...currentTransfer,\n target: {\n ...target,\n txHash: targetTxHash,\n },\n } satisfies TargetPendingTransfer;\n }\n\n if (!isHash(target.txHash)) {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid target transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n targetClient.waitForTransactionReceipt({\n // Wait for all required confirmations (which should just be 1 on target).\n // This is a safety in case it's ever more than 1. We just won't \"poll\" multiple times\n // like we do for the source.\n confirmations: target.requiredConfirmationCount,\n hash: target.txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Target transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n return {\n ...currentTransfer,\n completedAtMs: Date.now(),\n status: 'completed',\n target: {\n ...target,\n confirmationCount: await targetClient\n .getTransactionConfirmations({\n // hash: target.txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => target.requiredConfirmationCount),\n },\n } satisfies CompletedTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _getTargetTxHashEthToAva({\n contractAddress,\n latestBlockNumber,\n sourceTxHash,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n latestBlockNumber: bigint | undefined;\n sourceTxHash: string;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n const { fromBlock, toBlock } = _getLogSearchRange({\n latestBlockNumber,\n targetStartBlockNumber,\n });\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n event: {\n inputs: [\n {\n indexed: false,\n name: 'to',\n type: 'address',\n },\n {\n indexed: false,\n name: 'amount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'feeAddress',\n type: 'address',\n },\n {\n indexed: false,\n name: 'feeAmount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'originTxId',\n type: 'bytes32',\n },\n ],\n name: 'Mint',\n type: 'event',\n },\n fromBlock,\n toBlock,\n });\n\n // OriginTxId is not indexed. So it cannot be part of the filter.\n const foundLog = logs.find((log) => log.args.originTxId === sourceTxHash); // TODO: Is my assumption correct here? ie that we want to compare the sourceTxHash?\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n\nexport async function _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress,\n latestBlockNumber,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n evmConfig: EvmConfig;\n fromAddress: string;\n latestBlockNumber: bigint | undefined;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n if (!isAddress(fromAddress)) {\n return null;\n }\n\n const { fromBlock, toBlock } = _getLogSearchRange({\n latestBlockNumber,\n targetStartBlockNumber,\n });\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n args: {\n dst: fromAddress,\n src: evmConfig.walletAddresses.ethereum,\n },\n event: {\n inputs: [\n {\n indexed: true,\n name: 'src',\n type: 'address',\n },\n {\n indexed: true,\n name: 'dst',\n type: 'address',\n },\n {\n indexed: false,\n name: 'wad',\n type: 'uint256',\n },\n ],\n name: 'Transfer',\n type: 'event',\n },\n fromBlock,\n toBlock,\n });\n\n const foundLog = logs.at(-1);\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n\nfunction _getLogSearchRange({\n latestBlockNumber,\n targetStartBlockNumber,\n}: {\n latestBlockNumber: bigint | undefined;\n targetStartBlockNumber: bigint;\n}): LogSearchRange {\n const fromBlock =\n targetStartBlockNumber > MAX_LOG_BLOCK_RANGE ? targetStartBlockNumber - MAX_LOG_BLOCK_RANGE : 'earliest';\n const highestBlockNumber = targetStartBlockNumber + MAX_LOG_BLOCK_RANGE;\n const toBlock =\n latestBlockNumber !== undefined && highestBlockNumber < latestBlockNumber ? highestBlockNumber : 'latest';\n\n return {\n fromBlock,\n toBlock,\n };\n}\n\nasync function _waitForNextTargetHashPollingCycle({ signal }: { signal: AbortSignal }): Promise<void> {\n await awaitOrAbort(new Promise<void>((resolve) => setTimeout(resolve, TARGET_HASH_POLL_INTERVAL_MS)), signal);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ErrorCode as e}from"../../../errors.js";import{awaitOrAbort as t,getEvmClientForChain as n}from"../../_utils.js";import{getTransferData as r}from"../_utils/transfer-data.js";import{isAddress as i,isHash as a}from"viem";function
|
|
1
|
+
import{ErrorCode as e}from"../../../errors.js";import{awaitOrAbort as t,getEvmClientForChain as n}from"../../_utils.js";import{getTransferData as r}from"../_utils/transfer-data.js";import{isAddress as i,isHash as a}from"viem";const o=1024n;function s({config:t}){return({transfer:r,updateListener:i})=>{let o=new AbortController,s=()=>{o.abort()};if(r.status===`completed`||r.status===`failed`)return{cancel:s,result:Promise.resolve(r)};let{txHash:u}=r.source;if(!a(u)){let t={...r,errorCode:e.INVALID_PARAMS,errorReason:`Invalid source transaction hash`,failedAtMs:Date.now(),status:`failed`};return i(t),{cancel:s,result:Promise.resolve(t)}}let d=n({chain:r.sourceChain}),f=n({chain:r.targetChain}),m=structuredClone(r);return{cancel:s,result:(async()=>{for(;!o.signal.aborted;){let e=!1;switch(m.status){case`source-pending`:m=await c({currentTransfer:m,signal:o.signal,sourceClient:d}),i(m);break;case`target-pending`:m=await l({assetIn:m.sourceAsset,currentTransfer:m,evmConfig:t,signal:o.signal,sourceChainId:m.sourceChain.chainId,targetChainId:m.targetChain.chainId,targetClient:f}),e=m.status===`target-pending`&&!m.target?.txHash,i(m),e&&await p({signal:o.signal});break;default:return m}}return m})()}}}async function c({currentTransfer:n,signal:r,sourceClient:i}){let{confirmationCount:o,requiredConfirmationCount:s,txHash:c}=n.source;if(!a(c))return{...n,errorCode:e.INVALID_PARAMS,errorReason:`Invalid source transaction hash`,failedAtMs:Date.now(),status:`failed`};if(o>=s)return{...n,status:`target-pending`,target:{confirmationCount:0,requiredConfirmationCount:1,startedAtMs:Date.now()}};try{let a=await t(i.waitForTransactionReceipt({confirmations:Math.min(o+1,s),hash:c}),r);if(a.status===`aborted`)return n;let l=a.value;if(l.status===`reverted`)return{...n,errorCode:e.TRANSACTION_REVERTED,errorReason:`Source transaction was reverted`,failedAtMs:Date.now(),status:`failed`};let u=await i.getTransactionConfirmations({transactionReceipt:l}).then(e=>Number(e)).catch(()=>o+1);return u<s?{...n,source:{...n.source,confirmationCount:u}}:{...n,source:{...n.source,confirmationCount:u},status:`target-pending`,target:{confirmationCount:0,requiredConfirmationCount:1,startedAtMs:Date.now()}}}catch{return n}}async function l({assetIn:n,currentTransfer:i,evmConfig:o,signal:s,sourceChainId:c,targetChainId:l,targetClient:f}){let p=i.target??{confirmationCount:0,requiredConfirmationCount:1,startedAtMs:Date.now()};if(p.confirmationCount>=p.requiredConfirmationCount)return{...i,status:`completed`,completedAtMs:Date.now()};if(!p.txHash){let{ethToAva:e,target:{token:{address:t}}}=r({assetIn:n,sourceChainId:c,targetChainId:l},o),a=typeof f.getBlockNumber==`function`?await f.getBlockNumber().catch(()=>void 0):void 0,s=await(e?u({contractAddress:t,latestBlockNumber:a,sourceTxHash:i.source.txHash,targetClient:f,targetStartBlockNumber:i.source.targetStartBlockNumber}):d({contractAddress:t,evmConfig:o,fromAddress:i.fromAddress,latestBlockNumber:a,targetClient:f,targetStartBlockNumber:i.source.targetStartBlockNumber})).catch(()=>null);return s?{...i,target:{...p,txHash:s}}:a===void 0?i:{...i,source:{...i.source,targetStartBlockNumber:a}}}if(!a(p.txHash))return{...i,errorCode:e.INVALID_PARAMS,errorReason:`Invalid target transaction hash`,failedAtMs:Date.now(),status:`failed`};try{let n=await t(f.waitForTransactionReceipt({confirmations:p.requiredConfirmationCount,hash:p.txHash}),s);if(n.status===`aborted`)return i;let r=n.value;return r.status===`reverted`?{...i,errorCode:e.TRANSACTION_REVERTED,errorReason:`Target transaction was reverted`,failedAtMs:Date.now(),status:`failed`}:{...i,completedAtMs:Date.now(),status:`completed`,target:{...p,confirmationCount:await f.getTransactionConfirmations({transactionReceipt:r}).then(e=>Number(e)).catch(()=>p.requiredConfirmationCount)}}}catch{return i}}async function u({contractAddress:e,latestBlockNumber:t,sourceTxHash:n,targetClient:r,targetStartBlockNumber:i}){if(i===void 0)return null;let{fromBlock:a,toBlock:o}=f({latestBlockNumber:t,targetStartBlockNumber:i}),s=(await r.getLogs({address:e,event:{inputs:[{indexed:!1,name:`to`,type:`address`},{indexed:!1,name:`amount`,type:`uint256`},{indexed:!1,name:`feeAddress`,type:`address`},{indexed:!1,name:`feeAmount`,type:`uint256`},{indexed:!1,name:`originTxId`,type:`bytes32`}],name:`Mint`,type:`event`},fromBlock:a,toBlock:o})).find(e=>e.args.originTxId===n);return s?s.transactionHash:null}async function d({contractAddress:e,evmConfig:t,fromAddress:n,latestBlockNumber:r,targetClient:a,targetStartBlockNumber:o}){if(o===void 0||!i(n))return null;let{fromBlock:s,toBlock:c}=f({latestBlockNumber:r,targetStartBlockNumber:o}),l=(await a.getLogs({address:e,args:{dst:n,src:t.walletAddresses.ethereum},event:{inputs:[{indexed:!0,name:`src`,type:`address`},{indexed:!0,name:`dst`,type:`address`},{indexed:!1,name:`wad`,type:`uint256`}],name:`Transfer`,type:`event`},fromBlock:s,toBlock:c})).at(-1);return l?l.transactionHash:null}function f({latestBlockNumber:e,targetStartBlockNumber:t}){let n=t>o?t-o:`earliest`,r=t+o;return{fromBlock:n,toBlock:e!==void 0&&r<e?r:`latest`}}async function p({signal:e}){await t(new Promise(e=>setTimeout(e,15e3)),e)}export{s as trackTransferFactory};
|
|
2
2
|
//# sourceMappingURL=track-transfer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-transfer.js","names":[],"sources":["../../../../src/transfer-service/avalanche-evm/_handlers/track-transfer.ts"],"sourcesContent":["import { isAddress, isHash, type Hash } from 'viem';\nimport { ErrorCode } from '../../../errors';\nimport type { Asset } from '../../../types/asset';\nimport type { Caip2ChainId } from '../../../types/caip';\nimport type {\n CompletedTransfer,\n FailedTransfer,\n SourcePendingTransfer,\n TargetPendingTransfer,\n Transfer,\n} from '../../../types/transfer';\nimport type { TransferService } from '../../../types/service';\nimport { awaitOrAbort, getEvmClientForChain } from '../../_utils';\nimport type { EvmConfig } from '../_types';\nimport { getTransferData } from '../_utils/transfer-data';\n\nexport interface TrackTransferFactoryConfig {\n config: EvmConfig;\n}\n\nexport function trackTransferFactory({\n config: evmConfig,\n}: TrackTransferFactoryConfig): TransferService['trackTransfer'] {\n return ({ transfer, updateListener }) => {\n const ac = new AbortController();\n const cancel = () => {\n ac.abort();\n };\n\n // Failed and completed transfers are terminal states.\n if (transfer.status === 'completed' || transfer.status === 'failed') {\n return {\n cancel,\n result: Promise.resolve(transfer),\n };\n }\n\n const { txHash } = transfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...transfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n updateListener(failedTransfer);\n\n return {\n cancel,\n result: Promise.resolve(failedTransfer),\n };\n }\n\n const sourceClient = getEvmClientForChain({ chain: transfer.sourceChain });\n const targetClient = getEvmClientForChain({ chain: transfer.targetChain });\n let currentTransfer: Transfer = structuredClone(transfer);\n\n const executeTracking = async (): Promise<Transfer> => {\n // Create a loop to poll for confirmations and update the transfer status until\n // failed or completed.\n while (!ac.signal.aborted) {\n switch (currentTransfer.status) {\n case 'source-pending': {\n currentTransfer = await _trackSourceTx({\n currentTransfer,\n signal: ac.signal,\n sourceClient,\n });\n updateListener(currentTransfer);\n break;\n }\n case 'target-pending': {\n currentTransfer = await _trackTargetTx({\n assetIn: currentTransfer.sourceAsset,\n currentTransfer,\n evmConfig,\n signal: ac.signal,\n sourceChainId: currentTransfer.sourceChain.chainId,\n targetChainId: currentTransfer.targetChain.chainId,\n targetClient,\n });\n updateListener(currentTransfer);\n break;\n }\n default: {\n return currentTransfer;\n }\n }\n }\n\n return currentTransfer;\n };\n\n return {\n cancel,\n result: executeTracking(),\n };\n };\n}\n\nexport interface TrackBaseParams {\n currentTransfer: Transfer;\n signal: AbortSignal;\n}\n\nexport interface TrackSourceTxParams extends TrackBaseParams {\n currentTransfer: SourcePendingTransfer;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport interface TrackTargetTxParams extends TrackBaseParams {\n assetIn: Asset;\n currentTransfer: TargetPendingTransfer;\n evmConfig: EvmConfig;\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport async function _trackSourceTx({\n currentTransfer,\n signal,\n sourceClient,\n}: TrackSourceTxParams): Promise<Transfer> {\n const { confirmationCount, requiredConfirmationCount, txHash } = currentTransfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n return failedTransfer;\n }\n\n if (confirmationCount >= requiredConfirmationCount) {\n // Move to target-pending\n\n const updatedTransfer: TargetPendingTransfer = {\n ...currentTransfer,\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n };\n\n return updatedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n sourceClient.waitForTransactionReceipt({\n confirmations: Math.min(confirmationCount + 1, requiredConfirmationCount),\n hash: txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Source transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n // Either update confirmation count or move to target-pending\n const newConfirmationCount: number = await sourceClient\n .getTransactionConfirmations({\n // hash: txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => confirmationCount + 1);\n\n // If still not enough confirmations, just update count\n if (newConfirmationCount < requiredConfirmationCount) {\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n } satisfies SourcePendingTransfer;\n }\n\n // Move to target-pending\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n } satisfies TargetPendingTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _trackTargetTx({\n assetIn,\n currentTransfer,\n evmConfig,\n signal,\n sourceChainId,\n targetChainId,\n targetClient,\n}: TrackTargetTxParams): Promise<Transfer> {\n const target = currentTransfer.target ?? {\n confirmationCount: 0,\n requiredConfirmationCount: 1,\n startedAtMs: Date.now(),\n };\n\n if (target.confirmationCount >= target.requiredConfirmationCount) {\n // Move to completed\n const completedTransfer: Transfer = {\n ...currentTransfer,\n status: 'completed',\n completedAtMs: Date.now(),\n };\n return completedTransfer;\n }\n\n if (!target.txHash) {\n // Find the target tx hash via logs and then update the transfer.\n const {\n ethToAva,\n target: {\n token: { address: contractAddress },\n },\n } = getTransferData(\n {\n assetIn,\n sourceChainId,\n targetChainId,\n },\n evmConfig,\n );\n\n const targetTxHash = ethToAva\n ? await _getTargetTxHashEthToAva({\n contractAddress,\n sourceTxHash: currentTransfer.source.txHash,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n })\n : await _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress: currentTransfer.fromAddress,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n });\n\n if (!targetTxHash) {\n return currentTransfer;\n }\n\n return {\n ...currentTransfer,\n target: {\n ...target,\n txHash: targetTxHash,\n },\n } satisfies TargetPendingTransfer;\n }\n\n if (!isHash(target.txHash)) {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid target transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n targetClient.waitForTransactionReceipt({\n // Wait for all required confirmations (which should just be 1 on target).\n // This is a safety in case it's ever more than 1. We just won't \"poll\" multiple times\n // like we do for the source.\n confirmations: target.requiredConfirmationCount,\n hash: target.txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Target transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n return {\n ...currentTransfer,\n completedAtMs: Date.now(),\n status: 'completed',\n target: {\n ...target,\n confirmationCount: await targetClient\n .getTransactionConfirmations({\n // hash: target.txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => target.requiredConfirmationCount),\n },\n } satisfies CompletedTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _getTargetTxHashEthToAva({\n contractAddress,\n sourceTxHash,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n sourceTxHash: string;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n event: {\n inputs: [\n {\n indexed: false,\n name: 'to',\n type: 'address',\n },\n {\n indexed: false,\n name: 'amount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'feeAddress',\n type: 'address',\n },\n {\n indexed: false,\n name: 'feeAmount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'originTxId',\n type: 'bytes32',\n },\n ],\n name: 'Mint',\n type: 'event',\n },\n fromBlock: targetStartBlockNumber,\n toBlock: 'latest', // TODO: Consider limiting the range?\n });\n\n // OriginTxId is not indexed. So it cannot be part of the filter.\n const foundLog = logs.find((log) => log.args.originTxId === sourceTxHash); // TODO: Is my assumption correct here? ie that we want to compare the sourceTxHash?\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n\nexport async function _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n evmConfig: EvmConfig;\n fromAddress: string;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n if (!isAddress(fromAddress)) {\n return null;\n }\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n args: {\n dst: fromAddress,\n src: evmConfig.walletAddresses.ethereum,\n },\n event: {\n inputs: [\n {\n indexed: true,\n name: 'src',\n type: 'address',\n },\n {\n indexed: true,\n name: 'dst',\n type: 'address',\n },\n {\n indexed: false,\n name: 'wad',\n type: 'uint256',\n },\n ],\n name: 'Transfer',\n type: 'event',\n },\n fromBlock: targetStartBlockNumber,\n toBlock: 'latest', // TODO: Consider limiting the range?\n });\n\n const foundLog = logs.at(0); // TODO: Is this correct? Original code was using -1. But we want \"oldest\" log right? ie the closest to the \"fromBlock\"?\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"track-transfer.js","names":[],"sources":["../../../../src/transfer-service/avalanche-evm/_handlers/track-transfer.ts"],"sourcesContent":["import { isAddress, isHash, type Hash } from 'viem';\nimport { ErrorCode } from '../../../errors';\nimport type { Asset } from '../../../types/asset';\nimport type { Caip2ChainId } from '../../../types/caip';\nimport type {\n CompletedTransfer,\n FailedTransfer,\n SourcePendingTransfer,\n TargetPendingTransfer,\n Transfer,\n} from '../../../types/transfer';\nimport type { TransferService } from '../../../types/service';\nimport { awaitOrAbort, getEvmClientForChain } from '../../_utils';\nimport type { EvmConfig } from '../_types';\nimport { getTransferData } from '../_utils/transfer-data';\n\nconst MAX_LOG_BLOCK_RANGE = 1024n;\nconst TARGET_HASH_POLL_INTERVAL_MS = 15_000;\n\ntype LogSearchRange = {\n fromBlock: bigint | 'earliest';\n toBlock: bigint | 'latest';\n};\n\nexport interface TrackTransferFactoryConfig {\n config: EvmConfig;\n}\n\nexport function trackTransferFactory({\n config: evmConfig,\n}: TrackTransferFactoryConfig): TransferService['trackTransfer'] {\n return ({ transfer, updateListener }) => {\n const ac = new AbortController();\n const cancel = () => {\n ac.abort();\n };\n\n // Failed and completed transfers are terminal states.\n if (transfer.status === 'completed' || transfer.status === 'failed') {\n return {\n cancel,\n result: Promise.resolve(transfer),\n };\n }\n\n const { txHash } = transfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...transfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n updateListener(failedTransfer);\n\n return {\n cancel,\n result: Promise.resolve(failedTransfer),\n };\n }\n\n const sourceClient = getEvmClientForChain({ chain: transfer.sourceChain });\n const targetClient = getEvmClientForChain({ chain: transfer.targetChain });\n let currentTransfer: Transfer = structuredClone(transfer);\n\n const executeTracking = async (): Promise<Transfer> => {\n // Create a loop to poll for confirmations and update the transfer status until\n // failed or completed.\n while (!ac.signal.aborted) {\n let shouldDelayAfterUpdate = false;\n\n switch (currentTransfer.status) {\n case 'source-pending': {\n currentTransfer = await _trackSourceTx({\n currentTransfer,\n signal: ac.signal,\n sourceClient,\n });\n updateListener(currentTransfer);\n break;\n }\n case 'target-pending': {\n currentTransfer = await _trackTargetTx({\n assetIn: currentTransfer.sourceAsset,\n currentTransfer,\n evmConfig,\n signal: ac.signal,\n sourceChainId: currentTransfer.sourceChain.chainId,\n targetChainId: currentTransfer.targetChain.chainId,\n targetClient,\n });\n shouldDelayAfterUpdate = currentTransfer.status === 'target-pending' && !currentTransfer.target?.txHash;\n updateListener(currentTransfer);\n\n if (shouldDelayAfterUpdate) {\n await _waitForNextTargetHashPollingCycle({ signal: ac.signal });\n }\n\n break;\n }\n default: {\n return currentTransfer;\n }\n }\n }\n\n return currentTransfer;\n };\n\n return {\n cancel,\n result: executeTracking(),\n };\n };\n}\n\nexport interface TrackBaseParams {\n currentTransfer: Transfer;\n signal: AbortSignal;\n}\n\nexport interface TrackSourceTxParams extends TrackBaseParams {\n currentTransfer: SourcePendingTransfer;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport interface TrackTargetTxParams extends TrackBaseParams {\n assetIn: Asset;\n currentTransfer: TargetPendingTransfer;\n evmConfig: EvmConfig;\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n}\n\nexport async function _trackSourceTx({\n currentTransfer,\n signal,\n sourceClient,\n}: TrackSourceTxParams): Promise<Transfer> {\n const { confirmationCount, requiredConfirmationCount, txHash } = currentTransfer.source;\n\n if (!isHash(txHash)) {\n const failedTransfer: FailedTransfer = {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid source transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n };\n return failedTransfer;\n }\n\n if (confirmationCount >= requiredConfirmationCount) {\n // Move to target-pending\n\n const updatedTransfer: TargetPendingTransfer = {\n ...currentTransfer,\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n };\n\n return updatedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n sourceClient.waitForTransactionReceipt({\n confirmations: Math.min(confirmationCount + 1, requiredConfirmationCount),\n hash: txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Source transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n // Either update confirmation count or move to target-pending\n const newConfirmationCount: number = await sourceClient\n .getTransactionConfirmations({\n // hash: txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => confirmationCount + 1);\n\n // If still not enough confirmations, just update count\n if (newConfirmationCount < requiredConfirmationCount) {\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n } satisfies SourcePendingTransfer;\n }\n\n // Move to target-pending\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n confirmationCount: newConfirmationCount,\n },\n status: 'target-pending',\n target: {\n confirmationCount: 0,\n requiredConfirmationCount: 1, // EVM txs need 1 confirmation on target chain.\n startedAtMs: Date.now(),\n },\n } satisfies TargetPendingTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _trackTargetTx({\n assetIn,\n currentTransfer,\n evmConfig,\n signal,\n sourceChainId,\n targetChainId,\n targetClient,\n}: TrackTargetTxParams): Promise<Transfer> {\n const target = currentTransfer.target ?? {\n confirmationCount: 0,\n requiredConfirmationCount: 1,\n startedAtMs: Date.now(),\n };\n\n if (target.confirmationCount >= target.requiredConfirmationCount) {\n // Move to completed\n const completedTransfer: Transfer = {\n ...currentTransfer,\n status: 'completed',\n completedAtMs: Date.now(),\n };\n return completedTransfer;\n }\n\n if (!target.txHash) {\n // Find the target tx hash via logs and then update the transfer.\n const {\n ethToAva,\n target: {\n token: { address: contractAddress },\n },\n } = getTransferData(\n {\n assetIn,\n sourceChainId,\n targetChainId,\n },\n evmConfig,\n );\n\n const latestBlockNumber =\n typeof targetClient.getBlockNumber === 'function'\n ? await targetClient.getBlockNumber().catch(() => undefined)\n : undefined;\n\n const targetTxHash = await (ethToAva\n ? _getTargetTxHashEthToAva({\n contractAddress,\n latestBlockNumber,\n sourceTxHash: currentTransfer.source.txHash,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n })\n : _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress: currentTransfer.fromAddress,\n latestBlockNumber,\n targetClient,\n targetStartBlockNumber: currentTransfer.source.targetStartBlockNumber,\n })\n ).catch(() => null);\n\n if (!targetTxHash) {\n if (latestBlockNumber === undefined) {\n return currentTransfer;\n }\n\n return {\n ...currentTransfer,\n source: {\n ...currentTransfer.source,\n targetStartBlockNumber: latestBlockNumber,\n },\n } satisfies TargetPendingTransfer;\n }\n\n return {\n ...currentTransfer,\n target: {\n ...target,\n txHash: targetTxHash,\n },\n } satisfies TargetPendingTransfer;\n }\n\n if (!isHash(target.txHash)) {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.INVALID_PARAMS,\n errorReason: 'Invalid target transaction hash',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n try {\n const receiptResult = await awaitOrAbort(\n targetClient.waitForTransactionReceipt({\n // Wait for all required confirmations (which should just be 1 on target).\n // This is a safety in case it's ever more than 1. We just won't \"poll\" multiple times\n // like we do for the source.\n confirmations: target.requiredConfirmationCount,\n hash: target.txHash,\n }),\n signal,\n );\n\n if (receiptResult.status === 'aborted') {\n return currentTransfer;\n }\n\n const transactionReceipt = receiptResult.value;\n\n if (transactionReceipt.status === 'reverted') {\n return {\n ...currentTransfer,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'Target transaction was reverted',\n failedAtMs: Date.now(),\n status: 'failed',\n } satisfies FailedTransfer;\n }\n\n return {\n ...currentTransfer,\n completedAtMs: Date.now(),\n status: 'completed',\n target: {\n ...target,\n confirmationCount: await targetClient\n .getTransactionConfirmations({\n // hash: target.txHash,\n transactionReceipt,\n })\n .then((count) => Number(count))\n .catch(() => target.requiredConfirmationCount),\n },\n } satisfies CompletedTransfer;\n } catch {\n return currentTransfer;\n }\n}\n\nexport async function _getTargetTxHashEthToAva({\n contractAddress,\n latestBlockNumber,\n sourceTxHash,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n latestBlockNumber: bigint | undefined;\n sourceTxHash: string;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n const { fromBlock, toBlock } = _getLogSearchRange({\n latestBlockNumber,\n targetStartBlockNumber,\n });\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n event: {\n inputs: [\n {\n indexed: false,\n name: 'to',\n type: 'address',\n },\n {\n indexed: false,\n name: 'amount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'feeAddress',\n type: 'address',\n },\n {\n indexed: false,\n name: 'feeAmount',\n type: 'uint256',\n },\n {\n indexed: false,\n name: 'originTxId',\n type: 'bytes32',\n },\n ],\n name: 'Mint',\n type: 'event',\n },\n fromBlock,\n toBlock,\n });\n\n // OriginTxId is not indexed. So it cannot be part of the filter.\n const foundLog = logs.find((log) => log.args.originTxId === sourceTxHash); // TODO: Is my assumption correct here? ie that we want to compare the sourceTxHash?\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n\nexport async function _getTargetTxHashAvaToEth({\n contractAddress,\n evmConfig,\n fromAddress,\n latestBlockNumber,\n targetClient,\n targetStartBlockNumber,\n}: {\n contractAddress: Hash;\n evmConfig: EvmConfig;\n fromAddress: string;\n latestBlockNumber: bigint | undefined;\n targetClient: ReturnType<typeof getEvmClientForChain>;\n targetStartBlockNumber: bigint | undefined;\n}): Promise<Hash | null> {\n if (targetStartBlockNumber === undefined) {\n return null;\n }\n\n if (!isAddress(fromAddress)) {\n return null;\n }\n\n const { fromBlock, toBlock } = _getLogSearchRange({\n latestBlockNumber,\n targetStartBlockNumber,\n });\n\n const logs = await targetClient.getLogs({\n address: contractAddress,\n args: {\n dst: fromAddress,\n src: evmConfig.walletAddresses.ethereum,\n },\n event: {\n inputs: [\n {\n indexed: true,\n name: 'src',\n type: 'address',\n },\n {\n indexed: true,\n name: 'dst',\n type: 'address',\n },\n {\n indexed: false,\n name: 'wad',\n type: 'uint256',\n },\n ],\n name: 'Transfer',\n type: 'event',\n },\n fromBlock,\n toBlock,\n });\n\n const foundLog = logs.at(-1);\n\n if (!foundLog) {\n return null;\n }\n\n return foundLog.transactionHash;\n}\n\nfunction _getLogSearchRange({\n latestBlockNumber,\n targetStartBlockNumber,\n}: {\n latestBlockNumber: bigint | undefined;\n targetStartBlockNumber: bigint;\n}): LogSearchRange {\n const fromBlock =\n targetStartBlockNumber > MAX_LOG_BLOCK_RANGE ? targetStartBlockNumber - MAX_LOG_BLOCK_RANGE : 'earliest';\n const highestBlockNumber = targetStartBlockNumber + MAX_LOG_BLOCK_RANGE;\n const toBlock =\n latestBlockNumber !== undefined && highestBlockNumber < latestBlockNumber ? highestBlockNumber : 'latest';\n\n return {\n fromBlock,\n toBlock,\n };\n}\n\nasync function _waitForNextTargetHashPollingCycle({ signal }: { signal: AbortSignal }): Promise<void> {\n await awaitOrAbort(new Promise<void>((resolve) => setTimeout(resolve, TARGET_HASH_POLL_INTERVAL_MS)), signal);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../errors.cjs`),t=require(`../../../utils/caip.cjs`),n=require(`../../../_utils/chain.cjs`),r=require(`../../_utils.cjs`),i=require(`../../_evm-gas.cjs`),a=require(`../_api.cjs`),o=require(`../_utils.cjs`),s=require(`../_type-guards.cjs`);let
|
|
1
|
+
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../errors.cjs`),t=require(`../../../utils/caip.cjs`),n=require(`../../../_utils/chain.cjs`),r=require(`../../_utils.cjs`),i=require(`../../_evm-gas.cjs`),a=require(`../_api.cjs`),o=require(`../_utils.cjs`),s=require(`../_type-guards.cjs`),c=require(`../_solana-utils.cjs`);let l=require(`viem`),u=require(`@solana/kit`);function d(t){return async(r,i)=>{if(n.isEvmNamespace(r.sourceChain.chainId))return await f(r,i,t);if(n.isSolanaNamespace(r.sourceChain.chainId))return await p(r,i,t);throw new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Unsupported source chain namespace for estimateNativeFee: ${r.sourceChain.chainId}`)}}async function f(n,c,{apiOptions:u,appId:d}){if(!(0,l.isAddress)(n.fromAddress))throw new e.InvalidParamsError(e.ErrorReason.INVALID_PARAMS,`Invalid fromAddress: ${n.fromAddress}`);let f=r.getEvmClientForChain({chain:n.sourceChain}),p=o.assetToAddressString(n.assetIn,n.sourceChain.chainId);if(!(0,l.isAddress)(p))throw new e.SdkError(e.ErrorReason.INVALID_PARAMS,e.ErrorCode.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address. Can not call estimateGas.`});let h=o.isTokenAddressNative(p),g=n.sourceChain.chainId.toLowerCase()!==n.targetChain.chainId.toLowerCase(),_=0n;if(!h){let r,{address:i}=await a.markrGetSpenderAddress(u,{chainId:t.caip2ToEip155ChainId(n.sourceChain.chainId),crossChainSwap:g,quoteId:n.id});try{r=await f.readContract({address:p,abi:l.erc20Abi,functionName:`allowance`,args:[n.fromAddress,i]})}catch(t){throw new e.SdkError(`Error during allowance check`,e.ErrorCode.VIEM_ERROR,{cause:t,details:`Failed to read ERC20 allowance for Markr spender.`})}if(r<n.amountIn)try{_=await f.estimateGas({account:n.fromAddress,to:p,data:(0,l.encodeFunctionData)({abi:l.erc20Abi,functionName:`approve`,args:[i,n.amountIn]}),value:0n})}catch(t){throw new e.SdkError(`Error during gas estimation`,e.ErrorCode.VIEM_ERROR,{cause:t,details:`Failed to estimate gas for ERC20 approval transaction.`})}}let v=_>0n,y=o.calculateMarkrMinimumAmountOut({amountOut:n.amountOut,assetOut:n.assetOut,slippageBps:n.slippageBps}),b=await a.markrSwap(u,{amountIn:n.amountIn.toString(),appId:d,minAmountOut:y.toString(),tokenIn:p,tokenOut:o.assetToAddressString(n.assetOut,n.targetChain.chainId),uuid:n.id});if(!s.isEvmSwapResponse(b))throw new e.SdkError(e.ErrorReason.CHAIN_NOT_SUPPORTED,e.ErrorCode.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response with chainType ${b.chainType}.`});let x=0n;x=v?n.gasEstimate?r.applyFeeUnitsBpsMargin(n.gasEstimate,c?.feeUnitsMarginBps):r.applyFeeUnitsBpsMargin(n.fees.filter(e=>e.type===`gas`&&e.chainId===n.sourceChain.chainId).reduce((e,t)=>e+t.amount,0n)||700000n,c?.feeUnitsMarginBps):await m({crossChain:g,feeUnitsMarginBps:c?.feeUnitsMarginBps,fromAddress:n.fromAddress,sourceClient:f,swap:b});let S=await i.estimateEvmFeesPerGas(f,n.sourceChain,c?.overrides?.feeRateTier),C=c?.overrides?.maxFeePerGas??S.maxFeePerGas,w=c?.overrides?.maxPriorityFeePerGas??S.maxPriorityFeePerGas,T=(x+r.applyFeeUnitsBpsMargin(_,c?.feeUnitsMarginBps))*C;return{asset:n.sourceChain.networkToken,totalFee:T,meta:{approvalFee:v?r.applyFeeUnitsBpsMargin(_,c?.feeUnitsMarginBps)*C:void 0,maxFeePerGas:C,maxPriorityFeePerGas:w}}}async function p(t,n,{apiOptions:i,appId:l}){let d=o.assetToAddressString(t.assetIn,t.sourceChain.chainId);if(!(0,u.isAddress)(d))throw new e.SdkError(e.ErrorReason.INVALID_PARAMS,e.ErrorCode.INVALID_PARAMS,{details:`assetIn address is not a valid Solana address. Can not call estimateGas.`});let f=o.calculateMarkrMinimumAmountOut({amountOut:t.amountOut,assetOut:t.assetOut,slippageBps:t.slippageBps}),p=await a.markrSwap(i,{amountIn:t.amountIn.toString(),appId:l,minAmountOut:f.toString(),tokenIn:d,tokenOut:o.assetToAddressString(t.assetOut,t.targetChain.chainId),userPublicKey:t.fromAddress,uuid:t.id});if(!s.isSolanaSwapResponse(p))throw new e.SdkError(e.ErrorReason.CHAIN_NOT_SUPPORTED,e.ErrorCode.INVALID_PARAMS,{details:`Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.`});let m=r.getSolanaRpcForChain({chain:t.sourceChain}),v=await c.refreshSolanaSwapTransactionBlockhash(p.swapTransaction,m),{messageBytes:y}=c.decodeSolanaTransactionBase64(v),b=h(y),x=(await m.getFeeForMessage(b).send()).value;if(!x)throw new e.SdkError(`Failed to get fee for message after refreshing blockhash.`,e.ErrorCode.TIMEOUT,{details:`Unable to estimate Solana base fee because the transaction message remained invalid.`});let S=r.applyFeeUnitsBpsMargin((await m.simulateTransaction(v,{encoding:`base64`,sigVerify:!1,replaceRecentBlockhash:!0}).send()).value.unitsConsumed??0n,n?.feeUnitsMarginBps),C=g(y),w=S*C/1000000n,T=await _({fromAddress:t.fromAddress,rpc:m,transactionMessageBytes:y}),E=x+w+T.rentFee;return{asset:t.sourceChain.networkToken,totalFee:E,refundable:T.refundable,meta:{baseFee:x,priorityFee:w,computeUnitPriceMicroLamports:C,unitsConsumed:S,createdAccounts:T.createdAccounts,rentFee:T.rentFee,rentFeeNet:T.rentFeeNet}}}async function m({crossChain:t,feeUnitsMarginBps:n,fromAddress:i,sourceClient:a,swap:s}){try{return r.applyFeeUnitsBpsMargin(await a.estimateGas({account:i,to:s.to,data:s.data,value:s.value}),n)}catch(n){let r=`Failed to estimate gas for Markr swap transaction.`;try{let e=o.decodeMarkrRevertError(await o.getMarkrSwapWrapperAbi(t),n);e&&(r=`${r} Markr revert: ${e}.`)}catch{}throw new e.SdkError(`Error during gas estimation`,e.ErrorCode.VIEM_ERROR,{cause:n,details:r})}}function h(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function g(e){let t=(0,u.getCompiledTransactionMessageDecoder)().decode(e);for(let e of t.instructions){if(t.staticAccounts[e.programAddressIndex]!==`ComputeBudget111111111111111111111111111111`)continue;let n=e.data;if(!(!n||n.length<9)&&n[0]===3)return new DataView(n.buffer,n.byteOffset,n.byteLength).getBigUint64(1,!0)}return 0n}async function _({fromAddress:e,rpc:t,transactionMessageBytes:n}){let r=(0,u.getCompiledTransactionMessageDecoder)().decode(n),i=new Map,a=new Map,o;for(let e of r.instructions){let n=r.staticAccounts[e.programAddressIndex];if(n===`11111111111111111111111111111111`){let t=v(r,e,1),n=y(e.data);t&&n>0n&&i.set(t,{address:t,kind:`system`,lamports:n});continue}if(n===`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`){let n=e.data?.[0];if(!(n===void 0||n===0||n===1))continue;let a=v(r,e,1);if(!a||(await t.getAccountInfo(a).send()).value)continue;o===void 0&&(o=await t.getMinimumBalanceForRentExemption(165n).send()),i.set(a,{address:a,kind:`ata`,lamports:o});continue}if(!(n===`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`||n===`TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`)||e.data?.[0]!==9)continue;let s=v(r,e,0),c=v(r,e,1);s&&c&&a.set(s,c)}let s=(0,u.isAddress)(e)?e:void 0,c=Array.from(i.values()).map(e=>{let t=a.get(e.address),n=s!==void 0&&t===s;return{...e,refunded:n}}),l=c.reduce((e,t)=>e+t.lamports,0n),d=c.reduce((e,t)=>e+(t.refunded?t.lamports:0n),0n);return{createdAccounts:c,rentFee:l,rentFeeNet:l-d,refundable:d}}function v(e,t,n){let r=t.accountIndices?.[n];if(r!==void 0)return e.staticAccounts[r]}function y(e){if(!e||e.length<12)return 0n;let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=t.getUint32(0,!0);if(n===0&&e.length>=12)return t.getBigUint64(4,!0);if(n===3&&e.length>=44){let n=Number(t.getBigUint64(36,!0));if(!Number.isSafeInteger(n)||n<0)return 0n;let r=44+n;return r+8>e.length?0n:new DataView(e.buffer,e.byteOffset+r,8).getBigUint64(0,!0)}return 0n}exports._estimateGasFromSwapResponse=m,exports.estimateNativeFeeFactory=d;
|
|
2
2
|
//# sourceMappingURL=estimate-native-fee.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estimate-native-fee.cjs","names":["isEvmNamespace","isSolanaNamespace","InvalidParamsError","ErrorReason","getEvmClientForChain","assetToAddressString","SdkError","ErrorCode","isTokenAddressNative","markrGetSpenderAddress","caip2ToEip155ChainId","erc20Abi","calculateMarkrMinimumAmountOut","markrSwap","isEvmSwapResponse","applyFeeUnitsBpsMargin","estimateEvmFeesPerGas","isSolanaSwapResponse","getSolanaRpcForChain","decodeMarkrRevertError","getMarkrSwapWrapperAbi"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import {\n compileTransaction,\n decompileTransactionMessageFetchingLookupTables,\n type CompiledTransactionMessage,\n getCompiledTransactionMessageDecoder,\n getBase64EncodedWireTransaction,\n getTransactionDecoder,\n isAddress as isSolanaAddress,\n setTransactionMessageLifetimeUsingBlockhash,\n type Base64EncodedWireTransaction,\n type Rpc,\n type SolanaRpcApi,\n type TransactionMessageBytes,\n type TransactionMessageBytesBase64,\n} from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n decodeMarkrRevertError,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\n/**\n * Compute Budget program address.\n *\n * Used to detect `SetComputeUnitPrice` instructions when deriving\n * the Solana priority fee component from transaction message bytes.\n */\nconst SOLANA_COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';\n\n/**\n * System Program address.\n *\n * Used to detect account-creation instructions and extract lamports\n * funded upfront for newly created system accounts.\n */\nconst SOLANA_SYSTEM_PROGRAM = '11111111111111111111111111111111';\n\n/**\n * Associated Token Account (ATA) program address.\n *\n * Used to detect ATA create instructions so the estimator can include\n * rent-exempt funding when the ATA does not already exist on-chain.\n */\nconst SOLANA_ASSOCIATED_TOKEN_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';\n\n/**\n * Legacy SPL Token program address.\n *\n * Used to detect close-account instructions that may refund lamports\n * from temporary token accounts back to the sender.\n */\nconst SOLANA_SPL_TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\n\n/**\n * SPL Token-2022 program address.\n *\n * Used alongside the legacy token program for close-account detection\n * when computing expected refundable lamports.\n */\nconst SOLANA_SPL_TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';\n\n/**\n * SPL token account size for rent-exemption calculations.\n *\n * Used with `getMinimumBalanceForRentExemption` to estimate ATA\n * creation funding requirements.\n */\nconst SPL_TOKEN_ACCOUNT_SIZE_BYTES = 165n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\ninterface CreatedRentAccount {\n address: string;\n kind: 'ata' | 'system';\n lamports: bigint;\n refunded: boolean;\n}\n\ninterface SolanaRentEstimate {\n createdAccounts: CreatedRentAccount[];\n rentFee: bigint;\n rentFeeNet: bigint;\n refundable: bigint;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < quote.amountIn;\n\n if (approvalNeeded) {\n try {\n allowanceApprovalGas = await sourceClient.estimateGas({\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n }),\n value: 0n,\n });\n } catch (error) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response with chainType ${swap.chainType}.`,\n });\n }\n\n let gasWithMargin = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // Attempt to use the Markr provided `gasEstimate` first if available.\n // Otherwise we attempt to get the gas from the `fees` component that are applicable\n // to the source chain. If neither of those are available, we fall back to a hardcoded value.\n if (quote.gasEstimate) {\n gasWithMargin = applyFeeUnitsBpsMargin(quote.gasEstimate, options?.feeUnitsMarginBps);\n } else {\n const sourceGasFee = quote.fees\n .filter((fee) => fee.type === 'gas' && fee.chainId === quote.sourceChain.chainId)\n .reduce((acc, fee) => acc + fee.amount, 0n);\n\n gasWithMargin = applyFeeUnitsBpsMargin(\n sourceGasFee || EVM_SWAP_FALLBACK_GAS_ESTIMATE,\n options?.feeUnitsMarginBps,\n );\n }\n } else {\n gasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n feeUnitsMarginBps: options?.feeUnitsMarginBps,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFee =\n (gasWithMargin + applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps)) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee,\n meta: {\n approvalFee: allowanceApprovalIsNeeded\n ? applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps) * maxFeePerGas\n : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n\n const txBytes = base64ToBytes(swap.swapTransaction);\n const tx = getTransactionDecoder().decode(txBytes);\n\n let transactionMessageBytes = tx.messageBytes;\n let swapTransactionBase64 = swap.swapTransaction as Base64EncodedWireTransaction;\n\n const initialFeeForMessageResponse = await rpc\n .getFeeForMessage(_transactionMessageBytesToBase64(transactionMessageBytes))\n .send();\n\n let baseFeeLamports = initialFeeForMessageResponse.value;\n\n if (!baseFeeLamports) {\n const latestBlockhashResponse = await rpc.getLatestBlockhash().send();\n const compiledMessageWithLifetime = getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n const decompiledMessage = await decompileTransactionMessageFetchingLookupTables(compiledMessageWithLifetime, rpc);\n\n const refreshedTxMessage = setTransactionMessageLifetimeUsingBlockhash(\n latestBlockhashResponse.value,\n decompiledMessage,\n );\n\n const refreshedTx = compileTransaction(refreshedTxMessage);\n transactionMessageBytes = refreshedTx.messageBytes;\n swapTransactionBase64 = getBase64EncodedWireTransaction(refreshedTx) as Base64EncodedWireTransaction;\n\n const refreshedFeeForMessageResponse = await rpc\n .getFeeForMessage(_transactionMessageBytesToBase64(transactionMessageBytes))\n .send();\n\n baseFeeLamports = refreshedFeeForMessageResponse.value;\n }\n\n if (!baseFeeLamports) {\n throw new SdkError('Failed to get fee for message after refreshing blockhash.', ErrorCode.TIMEOUT, {\n details: 'Unable to estimate Solana base fee because the transaction message remained invalid.',\n });\n }\n\n const sim = await rpc\n .simulateTransaction(swapTransactionBase64, {\n encoding: 'base64',\n sigVerify: false,\n replaceRecentBlockhash: true, // Helps prevent block hash expiration issues during simulation.\n })\n .send();\n\n const unitsConsumed = applyFeeUnitsBpsMargin(sim.value.unitsConsumed ?? 0n, options?.feeUnitsMarginBps);\n // Lamports\n const computeUnitPriceMicroLamports = _getComputeUnitPriceMicroLamports(transactionMessageBytes);\n const priorityFee = (unitsConsumed * computeUnitPriceMicroLamports) / 1_000_000n;\n const rentEstimate = await _estimateRentFeesFromTransactionMessage({\n fromAddress: quote.fromAddress,\n rpc,\n transactionMessageBytes,\n });\n const totalLamports = baseFeeLamports + priorityFee + rentEstimate.rentFee;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: totalLamports,\n refundable: rentEstimate.refundable,\n meta: {\n baseFee: baseFeeLamports,\n priorityFee,\n computeUnitPriceMicroLamports,\n unitsConsumed,\n createdAccounts: rentEstimate.createdAccounts,\n rentFee: rentEstimate.rentFee,\n rentFeeNet: rentEstimate.rentFeeNet,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n try {\n const gasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n });\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n } catch (err) {\n let details = 'Failed to estimate gas for Markr swap transaction.';\n\n try {\n const markrSwapWrapperAbi = await getMarkrSwapWrapperAbi(crossChain);\n const decodedRevert = decodeMarkrRevertError(markrSwapWrapperAbi, err);\n\n if (decodedRevert) {\n details = `${details} Markr revert: ${decodedRevert}.`;\n }\n } catch {\n // Keep the base details message if revert decoding fails.\n }\n\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details,\n });\n }\n}\n\nfunction base64ToBytes(base64: string): Uint8Array {\n return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));\n}\n\nfunction _transactionMessageBytesToBase64(bytes: TransactionMessageBytes): TransactionMessageBytesBase64 {\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary) as TransactionMessageBytesBase64;\n}\n\nfunction _getComputeUnitPriceMicroLamports(transactionMessageBytes: TransactionMessageBytes): bigint {\n const compiledTxMessage = getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n for (const instruction of compiledTxMessage.instructions) {\n const programAddress = compiledTxMessage.staticAccounts[instruction.programAddressIndex];\n if (programAddress !== SOLANA_COMPUTE_BUDGET_PROGRAM) {\n continue;\n }\n\n const data = instruction.data;\n\n if (!data || data.length < 1 + 8) {\n continue;\n }\n\n // ComputeBudget: u8 discriminator, then args\n // discriminator 3 = SetComputeUnitPrice, args = u64 microLamports\n const discriminator = data[0];\n if (discriminator !== 3) {\n continue;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return view.getBigUint64(1, true);\n }\n\n return 0n;\n}\n\nasync function _estimateRentFeesFromTransactionMessage({\n fromAddress,\n rpc,\n transactionMessageBytes,\n}: {\n fromAddress: string;\n rpc: Rpc<SolanaRpcApi>;\n transactionMessageBytes: TransactionMessageBytes;\n}): Promise<SolanaRentEstimate> {\n const compiledMessage: CompiledTransactionMessage =\n getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n const createdAccounts = new Map<string, Omit<CreatedRentAccount, 'refunded'>>();\n const closedAccountDestinationByAddress = new Map<string, string>();\n\n let ataRentLamports: bigint | undefined;\n\n for (const instruction of compiledMessage.instructions) {\n const programAddress = compiledMessage.staticAccounts[instruction.programAddressIndex];\n\n if (programAddress === SOLANA_SYSTEM_PROGRAM) {\n const createdAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n const lamports = _getSystemCreateAccountLamports(instruction.data);\n if (createdAccountAddress && lamports > 0n) {\n createdAccounts.set(createdAccountAddress, {\n address: createdAccountAddress,\n kind: 'system',\n lamports,\n });\n }\n continue;\n }\n\n if (programAddress === SOLANA_ASSOCIATED_TOKEN_PROGRAM) {\n const discriminator = instruction.data?.[0];\n const isAssociatedTokenCreateInstruction =\n discriminator === undefined || discriminator === 0 || discriminator === 1;\n\n if (!isAssociatedTokenCreateInstruction) {\n continue;\n }\n\n const associatedTokenAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n if (!associatedTokenAddress) {\n continue;\n }\n\n const accountInfo = await rpc.getAccountInfo(associatedTokenAddress).send();\n if (accountInfo.value) {\n continue;\n }\n\n if (ataRentLamports === undefined) {\n ataRentLamports = await rpc.getMinimumBalanceForRentExemption(SPL_TOKEN_ACCOUNT_SIZE_BYTES).send();\n }\n\n createdAccounts.set(associatedTokenAddress, {\n address: associatedTokenAddress,\n kind: 'ata',\n lamports: ataRentLamports,\n });\n\n continue;\n }\n\n const isSplTokenProgram =\n programAddress === SOLANA_SPL_TOKEN_PROGRAM || programAddress === SOLANA_SPL_TOKEN_2022_PROGRAM;\n if (!isSplTokenProgram) {\n continue;\n }\n\n const discriminator = instruction.data?.[0];\n if (discriminator !== 9) {\n continue;\n }\n\n const closedAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 0);\n const refundDestinationAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n\n if (closedAccountAddress && refundDestinationAddress) {\n closedAccountDestinationByAddress.set(closedAccountAddress, refundDestinationAddress);\n }\n }\n\n const normalizedFromAddress = isSolanaAddress(fromAddress) ? fromAddress : undefined;\n const createdAccountEntries = Array.from(createdAccounts.values()).map((createdAccount) => {\n const refundDestinationAddress = closedAccountDestinationByAddress.get(createdAccount.address);\n const refunded = normalizedFromAddress !== undefined && refundDestinationAddress === normalizedFromAddress;\n\n return {\n ...createdAccount,\n refunded,\n } satisfies CreatedRentAccount;\n });\n\n const rentLamportsUpfront = createdAccountEntries.reduce((acc, account) => acc + account.lamports, 0n);\n const refundedLamports = createdAccountEntries.reduce(\n (acc, account) => acc + (account.refunded ? account.lamports : 0n),\n 0n,\n );\n const rentFeeNet = rentLamportsUpfront - refundedLamports;\n\n return {\n createdAccounts: createdAccountEntries,\n rentFee: rentLamportsUpfront,\n rentFeeNet,\n refundable: refundedLamports,\n };\n}\n\n/**\n * Resolves an account address used by a compiled instruction.\n *\n * Compiled instructions reference accounts by index into the transaction\n * message's `staticAccounts` list. This helper converts an instruction-local\n * `accountIndex` into the corresponding static account address.\n *\n * Returns `undefined` when the instruction does not provide an account at the\n * requested index.\n */\nfunction _getInstructionAccountAddress(\n message: CompiledTransactionMessage,\n instruction: CompiledTransactionMessage['instructions'][number],\n accountIndex: number,\n): CompiledTransactionMessage['staticAccounts'][number] | undefined {\n const messageAccountIndex = instruction.accountIndices?.[accountIndex];\n\n if (messageAccountIndex === undefined) {\n return undefined;\n }\n\n return message.staticAccounts[messageAccountIndex];\n}\n\n/**\n * Extracts lamports from System Program account-creation instruction data.\n *\n * Supported instruction layouts:\n * - `CreateAccount` (`u32 discriminator = 0`): lamports at byte offset `4`.\n * - `CreateAccountWithSeed` (`u32 discriminator = 3`): lamports are located\n * after the variable-length seed field.\n *\n * Returns `0n` when data is missing, malformed, or not one of the supported\n * System Program create-account variants.\n */\nfunction _getSystemCreateAccountLamports(data: CompiledTransactionMessage['instructions'][number]['data']): bigint {\n if (!data || data.length < 12) {\n return 0n;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const instructionDiscriminator = view.getUint32(0, true);\n\n if (instructionDiscriminator === 0 && data.length >= 12) {\n return view.getBigUint64(4, true);\n }\n\n if (instructionDiscriminator === 3 && data.length >= 44) {\n const seedLenOffset = 36;\n const seedLength = Number(view.getBigUint64(seedLenOffset, true));\n if (!Number.isSafeInteger(seedLength) || seedLength < 0) {\n return 0n;\n }\n\n const lamportsOffset = seedLenOffset + 8 + seedLength;\n if (lamportsOffset + 8 > data.length) {\n return 0n;\n }\n\n return new DataView(data.buffer, data.byteOffset + lamportsOffset, 8).getBigUint64(0, true);\n }\n\n return 0n;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"estimate-native-fee.cjs","names":["isEvmNamespace","isSolanaNamespace","InvalidParamsError","ErrorReason","getEvmClientForChain","assetToAddressString","SdkError","ErrorCode","isTokenAddressNative","markrGetSpenderAddress","caip2ToEip155ChainId","erc20Abi","calculateMarkrMinimumAmountOut","markrSwap","isEvmSwapResponse","applyFeeUnitsBpsMargin","estimateEvmFeesPerGas","isSolanaSwapResponse","getSolanaRpcForChain","refreshSolanaSwapTransactionBlockhash","decodeSolanaTransactionBase64","decodeMarkrRevertError","getMarkrSwapWrapperAbi"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import {\n type CompiledTransactionMessage,\n getCompiledTransactionMessageDecoder,\n isAddress as isSolanaAddress,\n type Rpc,\n type SolanaRpcApi,\n type TransactionMessageBytes,\n type TransactionMessageBytesBase64,\n} from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n decodeMarkrRevertError,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { decodeSolanaTransactionBase64, refreshSolanaSwapTransactionBlockhash } from '../_solana-utils';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\n/**\n * Compute Budget program address.\n *\n * Used to detect `SetComputeUnitPrice` instructions when deriving\n * the Solana priority fee component from transaction message bytes.\n */\nconst SOLANA_COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';\n\n/**\n * System Program address.\n *\n * Used to detect account-creation instructions and extract lamports\n * funded upfront for newly created system accounts.\n */\nconst SOLANA_SYSTEM_PROGRAM = '11111111111111111111111111111111';\n\n/**\n * Associated Token Account (ATA) program address.\n *\n * Used to detect ATA create instructions so the estimator can include\n * rent-exempt funding when the ATA does not already exist on-chain.\n */\nconst SOLANA_ASSOCIATED_TOKEN_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';\n\n/**\n * Legacy SPL Token program address.\n *\n * Used to detect close-account instructions that may refund lamports\n * from temporary token accounts back to the sender.\n */\nconst SOLANA_SPL_TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\n\n/**\n * SPL Token-2022 program address.\n *\n * Used alongside the legacy token program for close-account detection\n * when computing expected refundable lamports.\n */\nconst SOLANA_SPL_TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';\n\n/**\n * SPL token account size for rent-exemption calculations.\n *\n * Used with `getMinimumBalanceForRentExemption` to estimate ATA\n * creation funding requirements.\n */\nconst SPL_TOKEN_ACCOUNT_SIZE_BYTES = 165n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\ninterface CreatedRentAccount {\n address: string;\n kind: 'ata' | 'system';\n lamports: bigint;\n refunded: boolean;\n}\n\ninterface SolanaRentEstimate {\n createdAccounts: CreatedRentAccount[];\n rentFee: bigint;\n rentFeeNet: bigint;\n refundable: bigint;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < quote.amountIn;\n\n if (approvalNeeded) {\n try {\n allowanceApprovalGas = await sourceClient.estimateGas({\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n }),\n value: 0n,\n });\n } catch (error) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response with chainType ${swap.chainType}.`,\n });\n }\n\n let gasWithMargin = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // Attempt to use the Markr provided `gasEstimate` first if available.\n // Otherwise we attempt to get the gas from the `fees` component that are applicable\n // to the source chain. If neither of those are available, we fall back to a hardcoded value.\n if (quote.gasEstimate) {\n gasWithMargin = applyFeeUnitsBpsMargin(quote.gasEstimate, options?.feeUnitsMarginBps);\n } else {\n const sourceGasFee = quote.fees\n .filter((fee) => fee.type === 'gas' && fee.chainId === quote.sourceChain.chainId)\n .reduce((acc, fee) => acc + fee.amount, 0n);\n\n gasWithMargin = applyFeeUnitsBpsMargin(\n sourceGasFee || EVM_SWAP_FALLBACK_GAS_ESTIMATE,\n options?.feeUnitsMarginBps,\n );\n }\n } else {\n gasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n feeUnitsMarginBps: options?.feeUnitsMarginBps,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFee =\n (gasWithMargin + applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps)) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee,\n meta: {\n approvalFee: allowanceApprovalIsNeeded\n ? applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps) * maxFeePerGas\n : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n const refreshedSwapTransactionBase64 = await refreshSolanaSwapTransactionBlockhash(swap.swapTransaction, rpc);\n const { messageBytes: transactionMessageBytes } = decodeSolanaTransactionBase64(refreshedSwapTransactionBase64);\n const transactionMessageBytesBase64 = _transactionMessageBytesToBase64(transactionMessageBytes);\n\n const initialFeeForMessageResponse = await rpc.getFeeForMessage(transactionMessageBytesBase64).send();\n\n const baseFeeLamports = initialFeeForMessageResponse.value;\n\n if (!baseFeeLamports) {\n throw new SdkError('Failed to get fee for message after refreshing blockhash.', ErrorCode.TIMEOUT, {\n details: 'Unable to estimate Solana base fee because the transaction message remained invalid.',\n });\n }\n\n const sim = await rpc\n .simulateTransaction(refreshedSwapTransactionBase64, {\n encoding: 'base64',\n sigVerify: false,\n replaceRecentBlockhash: true, // Helps prevent block hash expiration issues during simulation.\n })\n .send();\n\n const unitsConsumed = applyFeeUnitsBpsMargin(sim.value.unitsConsumed ?? 0n, options?.feeUnitsMarginBps);\n // Lamports\n const computeUnitPriceMicroLamports = _getComputeUnitPriceMicroLamports(transactionMessageBytes);\n const priorityFee = (unitsConsumed * computeUnitPriceMicroLamports) / 1_000_000n;\n const rentEstimate = await _estimateRentFeesFromTransactionMessage({\n fromAddress: quote.fromAddress,\n rpc,\n transactionMessageBytes,\n });\n const totalLamports = baseFeeLamports + priorityFee + rentEstimate.rentFee;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: totalLamports,\n refundable: rentEstimate.refundable,\n meta: {\n baseFee: baseFeeLamports,\n priorityFee,\n computeUnitPriceMicroLamports,\n unitsConsumed,\n createdAccounts: rentEstimate.createdAccounts,\n rentFee: rentEstimate.rentFee,\n rentFeeNet: rentEstimate.rentFeeNet,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n try {\n const gasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n });\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n } catch (err) {\n let details = 'Failed to estimate gas for Markr swap transaction.';\n\n try {\n const markrSwapWrapperAbi = await getMarkrSwapWrapperAbi(crossChain);\n const decodedRevert = decodeMarkrRevertError(markrSwapWrapperAbi, err);\n\n if (decodedRevert) {\n details = `${details} Markr revert: ${decodedRevert}.`;\n }\n } catch {\n // Keep the base details message if revert decoding fails.\n }\n\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details,\n });\n }\n}\n\nfunction _transactionMessageBytesToBase64(bytes: TransactionMessageBytes): TransactionMessageBytesBase64 {\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary) as TransactionMessageBytesBase64;\n}\n\nfunction _getComputeUnitPriceMicroLamports(transactionMessageBytes: TransactionMessageBytes): bigint {\n const compiledTxMessage = getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n for (const instruction of compiledTxMessage.instructions) {\n const programAddress = compiledTxMessage.staticAccounts[instruction.programAddressIndex];\n if (programAddress !== SOLANA_COMPUTE_BUDGET_PROGRAM) {\n continue;\n }\n\n const data = instruction.data;\n\n if (!data || data.length < 1 + 8) {\n continue;\n }\n\n // ComputeBudget: u8 discriminator, then args\n // discriminator 3 = SetComputeUnitPrice, args = u64 microLamports\n const discriminator = data[0];\n if (discriminator !== 3) {\n continue;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return view.getBigUint64(1, true);\n }\n\n return 0n;\n}\n\nasync function _estimateRentFeesFromTransactionMessage({\n fromAddress,\n rpc,\n transactionMessageBytes,\n}: {\n fromAddress: string;\n rpc: Rpc<SolanaRpcApi>;\n transactionMessageBytes: TransactionMessageBytes;\n}): Promise<SolanaRentEstimate> {\n const compiledMessage: CompiledTransactionMessage =\n getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n const createdAccounts = new Map<string, Omit<CreatedRentAccount, 'refunded'>>();\n const closedAccountDestinationByAddress = new Map<string, string>();\n\n let ataRentLamports: bigint | undefined;\n\n for (const instruction of compiledMessage.instructions) {\n const programAddress = compiledMessage.staticAccounts[instruction.programAddressIndex];\n\n if (programAddress === SOLANA_SYSTEM_PROGRAM) {\n const createdAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n const lamports = _getSystemCreateAccountLamports(instruction.data);\n if (createdAccountAddress && lamports > 0n) {\n createdAccounts.set(createdAccountAddress, {\n address: createdAccountAddress,\n kind: 'system',\n lamports,\n });\n }\n continue;\n }\n\n if (programAddress === SOLANA_ASSOCIATED_TOKEN_PROGRAM) {\n const discriminator = instruction.data?.[0];\n const isAssociatedTokenCreateInstruction =\n discriminator === undefined || discriminator === 0 || discriminator === 1;\n\n if (!isAssociatedTokenCreateInstruction) {\n continue;\n }\n\n const associatedTokenAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n if (!associatedTokenAddress) {\n continue;\n }\n\n const accountInfo = await rpc.getAccountInfo(associatedTokenAddress).send();\n if (accountInfo.value) {\n continue;\n }\n\n if (ataRentLamports === undefined) {\n ataRentLamports = await rpc.getMinimumBalanceForRentExemption(SPL_TOKEN_ACCOUNT_SIZE_BYTES).send();\n }\n\n createdAccounts.set(associatedTokenAddress, {\n address: associatedTokenAddress,\n kind: 'ata',\n lamports: ataRentLamports,\n });\n\n continue;\n }\n\n const isSplTokenProgram =\n programAddress === SOLANA_SPL_TOKEN_PROGRAM || programAddress === SOLANA_SPL_TOKEN_2022_PROGRAM;\n if (!isSplTokenProgram) {\n continue;\n }\n\n const discriminator = instruction.data?.[0];\n if (discriminator !== 9) {\n continue;\n }\n\n const closedAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 0);\n const refundDestinationAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n\n if (closedAccountAddress && refundDestinationAddress) {\n closedAccountDestinationByAddress.set(closedAccountAddress, refundDestinationAddress);\n }\n }\n\n const normalizedFromAddress = isSolanaAddress(fromAddress) ? fromAddress : undefined;\n const createdAccountEntries = Array.from(createdAccounts.values()).map((createdAccount) => {\n const refundDestinationAddress = closedAccountDestinationByAddress.get(createdAccount.address);\n const refunded = normalizedFromAddress !== undefined && refundDestinationAddress === normalizedFromAddress;\n\n return {\n ...createdAccount,\n refunded,\n } satisfies CreatedRentAccount;\n });\n\n const rentLamportsUpfront = createdAccountEntries.reduce((acc, account) => acc + account.lamports, 0n);\n const refundedLamports = createdAccountEntries.reduce(\n (acc, account) => acc + (account.refunded ? account.lamports : 0n),\n 0n,\n );\n const rentFeeNet = rentLamportsUpfront - refundedLamports;\n\n return {\n createdAccounts: createdAccountEntries,\n rentFee: rentLamportsUpfront,\n rentFeeNet,\n refundable: refundedLamports,\n };\n}\n\n/**\n * Resolves an account address used by a compiled instruction.\n *\n * Compiled instructions reference accounts by index into the transaction\n * message's `staticAccounts` list. This helper converts an instruction-local\n * `accountIndex` into the corresponding static account address.\n *\n * Returns `undefined` when the instruction does not provide an account at the\n * requested index.\n */\nfunction _getInstructionAccountAddress(\n message: CompiledTransactionMessage,\n instruction: CompiledTransactionMessage['instructions'][number],\n accountIndex: number,\n): CompiledTransactionMessage['staticAccounts'][number] | undefined {\n const messageAccountIndex = instruction.accountIndices?.[accountIndex];\n\n if (messageAccountIndex === undefined) {\n return undefined;\n }\n\n return message.staticAccounts[messageAccountIndex];\n}\n\n/**\n * Extracts lamports from System Program account-creation instruction data.\n *\n * Supported instruction layouts:\n * - `CreateAccount` (`u32 discriminator = 0`): lamports at byte offset `4`.\n * - `CreateAccountWithSeed` (`u32 discriminator = 3`): lamports are located\n * after the variable-length seed field.\n *\n * Returns `0n` when data is missing, malformed, or not one of the supported\n * System Program create-account variants.\n */\nfunction _getSystemCreateAccountLamports(data: CompiledTransactionMessage['instructions'][number]['data']): bigint {\n if (!data || data.length < 12) {\n return 0n;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const instructionDiscriminator = view.getUint32(0, true);\n\n if (instructionDiscriminator === 0 && data.length >= 12) {\n return view.getBigUint64(4, true);\n }\n\n if (instructionDiscriminator === 3 && data.length >= 44) {\n const seedLenOffset = 36;\n const seedLength = Number(view.getBigUint64(seedLenOffset, true));\n if (!Number.isSafeInteger(seedLength) || seedLength < 0) {\n return 0n;\n }\n\n const lamportsOffset = seedLenOffset + 8 + seedLength;\n if (lamportsOffset + 8 > data.length) {\n return 0n;\n }\n\n return new DataView(data.buffer, data.byteOffset + lamportsOffset, 8).getBigUint64(0, true);\n }\n\n return 0n;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ErrorCode as e,ErrorReason as t,InvalidParamsError as n,SdkError as r}from"../../../errors.js";import{caip2ToEip155ChainId as i}from"../../../utils/caip.js";import{isEvmNamespace as a,isSolanaNamespace as o}from"../../../_utils/chain.js";import{applyFeeUnitsBpsMargin as s,getEvmClientForChain as c,getSolanaRpcForChain as l}from"../../_utils.js";import{estimateEvmFeesPerGas as u}from"../../_evm-gas.js";import{markrGetSpenderAddress as d,markrSwap as f}from"../_api.js";import{assetToAddressString as p,calculateMarkrMinimumAmountOut as m,decodeMarkrRevertError as h,getMarkrSwapWrapperAbi as g,isTokenAddressNative as _}from"../_utils.js";import{isEvmSwapResponse as v,isSolanaSwapResponse as y}from"../_type-guards.js";import{
|
|
1
|
+
import{ErrorCode as e,ErrorReason as t,InvalidParamsError as n,SdkError as r}from"../../../errors.js";import{caip2ToEip155ChainId as i}from"../../../utils/caip.js";import{isEvmNamespace as a,isSolanaNamespace as o}from"../../../_utils/chain.js";import{applyFeeUnitsBpsMargin as s,getEvmClientForChain as c,getSolanaRpcForChain as l}from"../../_utils.js";import{estimateEvmFeesPerGas as u}from"../../_evm-gas.js";import{markrGetSpenderAddress as d,markrSwap as f}from"../_api.js";import{assetToAddressString as p,calculateMarkrMinimumAmountOut as m,decodeMarkrRevertError as h,getMarkrSwapWrapperAbi as g,isTokenAddressNative as _}from"../_utils.js";import{isEvmSwapResponse as v,isSolanaSwapResponse as y}from"../_type-guards.js";import{decodeSolanaTransactionBase64 as b,refreshSolanaSwapTransactionBlockhash as x}from"../_solana-utils.js";import{encodeFunctionData as S,erc20Abi as C,isAddress as w}from"viem";import{getCompiledTransactionMessageDecoder as T,isAddress as E}from"@solana/kit";function D(e){return async(r,i)=>{if(a(r.sourceChain.chainId))return await O(r,i,e);if(o(r.sourceChain.chainId))return await k(r,i,e);throw new n(t.INVALID_PARAMS,`Unsupported source chain namespace for estimateNativeFee: ${r.sourceChain.chainId}`)}}async function O(a,o,{apiOptions:l,appId:h}){if(!w(a.fromAddress))throw new n(t.INVALID_PARAMS,`Invalid fromAddress: ${a.fromAddress}`);let g=c({chain:a.sourceChain}),y=p(a.assetIn,a.sourceChain.chainId);if(!w(y))throw new r(t.INVALID_PARAMS,e.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address. Can not call estimateGas.`});let b=_(y),x=a.sourceChain.chainId.toLowerCase()!==a.targetChain.chainId.toLowerCase(),T=0n;if(!b){let t,{address:n}=await d(l,{chainId:i(a.sourceChain.chainId),crossChainSwap:x,quoteId:a.id});try{t=await g.readContract({address:y,abi:C,functionName:`allowance`,args:[a.fromAddress,n]})}catch(t){throw new r(`Error during allowance check`,e.VIEM_ERROR,{cause:t,details:`Failed to read ERC20 allowance for Markr spender.`})}if(t<a.amountIn)try{T=await g.estimateGas({account:a.fromAddress,to:y,data:S({abi:C,functionName:`approve`,args:[n,a.amountIn]}),value:0n})}catch(t){throw new r(`Error during gas estimation`,e.VIEM_ERROR,{cause:t,details:`Failed to estimate gas for ERC20 approval transaction.`})}}let E=T>0n,D=m({amountOut:a.amountOut,assetOut:a.assetOut,slippageBps:a.slippageBps}),O=await f(l,{amountIn:a.amountIn.toString(),appId:h,minAmountOut:D.toString(),tokenIn:y,tokenOut:p(a.assetOut,a.targetChain.chainId),uuid:a.id});if(!v(O))throw new r(t.CHAIN_NOT_SUPPORTED,e.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response with chainType ${O.chainType}.`});let k=0n;k=E?a.gasEstimate?s(a.gasEstimate,o?.feeUnitsMarginBps):s(a.fees.filter(e=>e.type===`gas`&&e.chainId===a.sourceChain.chainId).reduce((e,t)=>e+t.amount,0n)||700000n,o?.feeUnitsMarginBps):await A({crossChain:x,feeUnitsMarginBps:o?.feeUnitsMarginBps,fromAddress:a.fromAddress,sourceClient:g,swap:O});let j=await u(g,a.sourceChain,o?.overrides?.feeRateTier),M=o?.overrides?.maxFeePerGas??j.maxFeePerGas,N=o?.overrides?.maxPriorityFeePerGas??j.maxPriorityFeePerGas,P=(k+s(T,o?.feeUnitsMarginBps))*M;return{asset:a.sourceChain.networkToken,totalFee:P,meta:{approvalFee:E?s(T,o?.feeUnitsMarginBps)*M:void 0,maxFeePerGas:M,maxPriorityFeePerGas:N}}}async function k(n,i,{apiOptions:a,appId:o}){let c=p(n.assetIn,n.sourceChain.chainId);if(!E(c))throw new r(t.INVALID_PARAMS,e.INVALID_PARAMS,{details:`assetIn address is not a valid Solana address. Can not call estimateGas.`});let u=m({amountOut:n.amountOut,assetOut:n.assetOut,slippageBps:n.slippageBps}),d=await f(a,{amountIn:n.amountIn.toString(),appId:o,minAmountOut:u.toString(),tokenIn:c,tokenOut:p(n.assetOut,n.targetChain.chainId),userPublicKey:n.fromAddress,uuid:n.id});if(!y(d))throw new r(t.CHAIN_NOT_SUPPORTED,e.INVALID_PARAMS,{details:`Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.`});let h=l({chain:n.sourceChain}),g=await x(d.swapTransaction,h),{messageBytes:_}=b(g),v=j(_),S=(await h.getFeeForMessage(v).send()).value;if(!S)throw new r(`Failed to get fee for message after refreshing blockhash.`,e.TIMEOUT,{details:`Unable to estimate Solana base fee because the transaction message remained invalid.`});let C=s((await h.simulateTransaction(g,{encoding:`base64`,sigVerify:!1,replaceRecentBlockhash:!0}).send()).value.unitsConsumed??0n,i?.feeUnitsMarginBps),w=M(_),T=C*w/1000000n,D=await N({fromAddress:n.fromAddress,rpc:h,transactionMessageBytes:_}),O=S+T+D.rentFee;return{asset:n.sourceChain.networkToken,totalFee:O,refundable:D.refundable,meta:{baseFee:S,priorityFee:T,computeUnitPriceMicroLamports:w,unitsConsumed:C,createdAccounts:D.createdAccounts,rentFee:D.rentFee,rentFeeNet:D.rentFeeNet}}}async function A({crossChain:t,feeUnitsMarginBps:n,fromAddress:i,sourceClient:a,swap:o}){try{return s(await a.estimateGas({account:i,to:o.to,data:o.data,value:o.value}),n)}catch(n){let i=`Failed to estimate gas for Markr swap transaction.`;try{let e=h(await g(t),n);e&&(i=`${i} Markr revert: ${e}.`)}catch{}throw new r(`Error during gas estimation`,e.VIEM_ERROR,{cause:n,details:i})}}function j(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function M(e){let t=T().decode(e);for(let e of t.instructions){if(t.staticAccounts[e.programAddressIndex]!==`ComputeBudget111111111111111111111111111111`)continue;let n=e.data;if(!(!n||n.length<9)&&n[0]===3)return new DataView(n.buffer,n.byteOffset,n.byteLength).getBigUint64(1,!0)}return 0n}async function N({fromAddress:e,rpc:t,transactionMessageBytes:n}){let r=T().decode(n),i=new Map,a=new Map,o;for(let e of r.instructions){let n=r.staticAccounts[e.programAddressIndex];if(n===`11111111111111111111111111111111`){let t=P(r,e,1),n=F(e.data);t&&n>0n&&i.set(t,{address:t,kind:`system`,lamports:n});continue}if(n===`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`){let n=e.data?.[0];if(!(n===void 0||n===0||n===1))continue;let a=P(r,e,1);if(!a||(await t.getAccountInfo(a).send()).value)continue;o===void 0&&(o=await t.getMinimumBalanceForRentExemption(165n).send()),i.set(a,{address:a,kind:`ata`,lamports:o});continue}if(!(n===`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`||n===`TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`)||e.data?.[0]!==9)continue;let s=P(r,e,0),c=P(r,e,1);s&&c&&a.set(s,c)}let s=E(e)?e:void 0,c=Array.from(i.values()).map(e=>{let t=a.get(e.address),n=s!==void 0&&t===s;return{...e,refunded:n}}),l=c.reduce((e,t)=>e+t.lamports,0n),u=c.reduce((e,t)=>e+(t.refunded?t.lamports:0n),0n);return{createdAccounts:c,rentFee:l,rentFeeNet:l-u,refundable:u}}function P(e,t,n){let r=t.accountIndices?.[n];if(r!==void 0)return e.staticAccounts[r]}function F(e){if(!e||e.length<12)return 0n;let t=new DataView(e.buffer,e.byteOffset,e.byteLength),n=t.getUint32(0,!0);if(n===0&&e.length>=12)return t.getBigUint64(4,!0);if(n===3&&e.length>=44){let n=Number(t.getBigUint64(36,!0));if(!Number.isSafeInteger(n)||n<0)return 0n;let r=44+n;return r+8>e.length?0n:new DataView(e.buffer,e.byteOffset+r,8).getBigUint64(0,!0)}return 0n}export{A as _estimateGasFromSwapResponse,D as estimateNativeFeeFactory};
|
|
2
2
|
//# sourceMappingURL=estimate-native-fee.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estimate-native-fee.js","names":["isEvmAddress","isSolanaAddress"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import {\n compileTransaction,\n decompileTransactionMessageFetchingLookupTables,\n type CompiledTransactionMessage,\n getCompiledTransactionMessageDecoder,\n getBase64EncodedWireTransaction,\n getTransactionDecoder,\n isAddress as isSolanaAddress,\n setTransactionMessageLifetimeUsingBlockhash,\n type Base64EncodedWireTransaction,\n type Rpc,\n type SolanaRpcApi,\n type TransactionMessageBytes,\n type TransactionMessageBytesBase64,\n} from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n decodeMarkrRevertError,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\n/**\n * Compute Budget program address.\n *\n * Used to detect `SetComputeUnitPrice` instructions when deriving\n * the Solana priority fee component from transaction message bytes.\n */\nconst SOLANA_COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';\n\n/**\n * System Program address.\n *\n * Used to detect account-creation instructions and extract lamports\n * funded upfront for newly created system accounts.\n */\nconst SOLANA_SYSTEM_PROGRAM = '11111111111111111111111111111111';\n\n/**\n * Associated Token Account (ATA) program address.\n *\n * Used to detect ATA create instructions so the estimator can include\n * rent-exempt funding when the ATA does not already exist on-chain.\n */\nconst SOLANA_ASSOCIATED_TOKEN_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';\n\n/**\n * Legacy SPL Token program address.\n *\n * Used to detect close-account instructions that may refund lamports\n * from temporary token accounts back to the sender.\n */\nconst SOLANA_SPL_TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\n\n/**\n * SPL Token-2022 program address.\n *\n * Used alongside the legacy token program for close-account detection\n * when computing expected refundable lamports.\n */\nconst SOLANA_SPL_TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';\n\n/**\n * SPL token account size for rent-exemption calculations.\n *\n * Used with `getMinimumBalanceForRentExemption` to estimate ATA\n * creation funding requirements.\n */\nconst SPL_TOKEN_ACCOUNT_SIZE_BYTES = 165n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\ninterface CreatedRentAccount {\n address: string;\n kind: 'ata' | 'system';\n lamports: bigint;\n refunded: boolean;\n}\n\ninterface SolanaRentEstimate {\n createdAccounts: CreatedRentAccount[];\n rentFee: bigint;\n rentFeeNet: bigint;\n refundable: bigint;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < quote.amountIn;\n\n if (approvalNeeded) {\n try {\n allowanceApprovalGas = await sourceClient.estimateGas({\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n }),\n value: 0n,\n });\n } catch (error) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response with chainType ${swap.chainType}.`,\n });\n }\n\n let gasWithMargin = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // Attempt to use the Markr provided `gasEstimate` first if available.\n // Otherwise we attempt to get the gas from the `fees` component that are applicable\n // to the source chain. If neither of those are available, we fall back to a hardcoded value.\n if (quote.gasEstimate) {\n gasWithMargin = applyFeeUnitsBpsMargin(quote.gasEstimate, options?.feeUnitsMarginBps);\n } else {\n const sourceGasFee = quote.fees\n .filter((fee) => fee.type === 'gas' && fee.chainId === quote.sourceChain.chainId)\n .reduce((acc, fee) => acc + fee.amount, 0n);\n\n gasWithMargin = applyFeeUnitsBpsMargin(\n sourceGasFee || EVM_SWAP_FALLBACK_GAS_ESTIMATE,\n options?.feeUnitsMarginBps,\n );\n }\n } else {\n gasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n feeUnitsMarginBps: options?.feeUnitsMarginBps,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFee =\n (gasWithMargin + applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps)) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee,\n meta: {\n approvalFee: allowanceApprovalIsNeeded\n ? applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps) * maxFeePerGas\n : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n\n const txBytes = base64ToBytes(swap.swapTransaction);\n const tx = getTransactionDecoder().decode(txBytes);\n\n let transactionMessageBytes = tx.messageBytes;\n let swapTransactionBase64 = swap.swapTransaction as Base64EncodedWireTransaction;\n\n const initialFeeForMessageResponse = await rpc\n .getFeeForMessage(_transactionMessageBytesToBase64(transactionMessageBytes))\n .send();\n\n let baseFeeLamports = initialFeeForMessageResponse.value;\n\n if (!baseFeeLamports) {\n const latestBlockhashResponse = await rpc.getLatestBlockhash().send();\n const compiledMessageWithLifetime = getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n const decompiledMessage = await decompileTransactionMessageFetchingLookupTables(compiledMessageWithLifetime, rpc);\n\n const refreshedTxMessage = setTransactionMessageLifetimeUsingBlockhash(\n latestBlockhashResponse.value,\n decompiledMessage,\n );\n\n const refreshedTx = compileTransaction(refreshedTxMessage);\n transactionMessageBytes = refreshedTx.messageBytes;\n swapTransactionBase64 = getBase64EncodedWireTransaction(refreshedTx) as Base64EncodedWireTransaction;\n\n const refreshedFeeForMessageResponse = await rpc\n .getFeeForMessage(_transactionMessageBytesToBase64(transactionMessageBytes))\n .send();\n\n baseFeeLamports = refreshedFeeForMessageResponse.value;\n }\n\n if (!baseFeeLamports) {\n throw new SdkError('Failed to get fee for message after refreshing blockhash.', ErrorCode.TIMEOUT, {\n details: 'Unable to estimate Solana base fee because the transaction message remained invalid.',\n });\n }\n\n const sim = await rpc\n .simulateTransaction(swapTransactionBase64, {\n encoding: 'base64',\n sigVerify: false,\n replaceRecentBlockhash: true, // Helps prevent block hash expiration issues during simulation.\n })\n .send();\n\n const unitsConsumed = applyFeeUnitsBpsMargin(sim.value.unitsConsumed ?? 0n, options?.feeUnitsMarginBps);\n // Lamports\n const computeUnitPriceMicroLamports = _getComputeUnitPriceMicroLamports(transactionMessageBytes);\n const priorityFee = (unitsConsumed * computeUnitPriceMicroLamports) / 1_000_000n;\n const rentEstimate = await _estimateRentFeesFromTransactionMessage({\n fromAddress: quote.fromAddress,\n rpc,\n transactionMessageBytes,\n });\n const totalLamports = baseFeeLamports + priorityFee + rentEstimate.rentFee;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: totalLamports,\n refundable: rentEstimate.refundable,\n meta: {\n baseFee: baseFeeLamports,\n priorityFee,\n computeUnitPriceMicroLamports,\n unitsConsumed,\n createdAccounts: rentEstimate.createdAccounts,\n rentFee: rentEstimate.rentFee,\n rentFeeNet: rentEstimate.rentFeeNet,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n try {\n const gasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n });\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n } catch (err) {\n let details = 'Failed to estimate gas for Markr swap transaction.';\n\n try {\n const markrSwapWrapperAbi = await getMarkrSwapWrapperAbi(crossChain);\n const decodedRevert = decodeMarkrRevertError(markrSwapWrapperAbi, err);\n\n if (decodedRevert) {\n details = `${details} Markr revert: ${decodedRevert}.`;\n }\n } catch {\n // Keep the base details message if revert decoding fails.\n }\n\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details,\n });\n }\n}\n\nfunction base64ToBytes(base64: string): Uint8Array {\n return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));\n}\n\nfunction _transactionMessageBytesToBase64(bytes: TransactionMessageBytes): TransactionMessageBytesBase64 {\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary) as TransactionMessageBytesBase64;\n}\n\nfunction _getComputeUnitPriceMicroLamports(transactionMessageBytes: TransactionMessageBytes): bigint {\n const compiledTxMessage = getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n for (const instruction of compiledTxMessage.instructions) {\n const programAddress = compiledTxMessage.staticAccounts[instruction.programAddressIndex];\n if (programAddress !== SOLANA_COMPUTE_BUDGET_PROGRAM) {\n continue;\n }\n\n const data = instruction.data;\n\n if (!data || data.length < 1 + 8) {\n continue;\n }\n\n // ComputeBudget: u8 discriminator, then args\n // discriminator 3 = SetComputeUnitPrice, args = u64 microLamports\n const discriminator = data[0];\n if (discriminator !== 3) {\n continue;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return view.getBigUint64(1, true);\n }\n\n return 0n;\n}\n\nasync function _estimateRentFeesFromTransactionMessage({\n fromAddress,\n rpc,\n transactionMessageBytes,\n}: {\n fromAddress: string;\n rpc: Rpc<SolanaRpcApi>;\n transactionMessageBytes: TransactionMessageBytes;\n}): Promise<SolanaRentEstimate> {\n const compiledMessage: CompiledTransactionMessage =\n getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n const createdAccounts = new Map<string, Omit<CreatedRentAccount, 'refunded'>>();\n const closedAccountDestinationByAddress = new Map<string, string>();\n\n let ataRentLamports: bigint | undefined;\n\n for (const instruction of compiledMessage.instructions) {\n const programAddress = compiledMessage.staticAccounts[instruction.programAddressIndex];\n\n if (programAddress === SOLANA_SYSTEM_PROGRAM) {\n const createdAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n const lamports = _getSystemCreateAccountLamports(instruction.data);\n if (createdAccountAddress && lamports > 0n) {\n createdAccounts.set(createdAccountAddress, {\n address: createdAccountAddress,\n kind: 'system',\n lamports,\n });\n }\n continue;\n }\n\n if (programAddress === SOLANA_ASSOCIATED_TOKEN_PROGRAM) {\n const discriminator = instruction.data?.[0];\n const isAssociatedTokenCreateInstruction =\n discriminator === undefined || discriminator === 0 || discriminator === 1;\n\n if (!isAssociatedTokenCreateInstruction) {\n continue;\n }\n\n const associatedTokenAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n if (!associatedTokenAddress) {\n continue;\n }\n\n const accountInfo = await rpc.getAccountInfo(associatedTokenAddress).send();\n if (accountInfo.value) {\n continue;\n }\n\n if (ataRentLamports === undefined) {\n ataRentLamports = await rpc.getMinimumBalanceForRentExemption(SPL_TOKEN_ACCOUNT_SIZE_BYTES).send();\n }\n\n createdAccounts.set(associatedTokenAddress, {\n address: associatedTokenAddress,\n kind: 'ata',\n lamports: ataRentLamports,\n });\n\n continue;\n }\n\n const isSplTokenProgram =\n programAddress === SOLANA_SPL_TOKEN_PROGRAM || programAddress === SOLANA_SPL_TOKEN_2022_PROGRAM;\n if (!isSplTokenProgram) {\n continue;\n }\n\n const discriminator = instruction.data?.[0];\n if (discriminator !== 9) {\n continue;\n }\n\n const closedAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 0);\n const refundDestinationAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n\n if (closedAccountAddress && refundDestinationAddress) {\n closedAccountDestinationByAddress.set(closedAccountAddress, refundDestinationAddress);\n }\n }\n\n const normalizedFromAddress = isSolanaAddress(fromAddress) ? fromAddress : undefined;\n const createdAccountEntries = Array.from(createdAccounts.values()).map((createdAccount) => {\n const refundDestinationAddress = closedAccountDestinationByAddress.get(createdAccount.address);\n const refunded = normalizedFromAddress !== undefined && refundDestinationAddress === normalizedFromAddress;\n\n return {\n ...createdAccount,\n refunded,\n } satisfies CreatedRentAccount;\n });\n\n const rentLamportsUpfront = createdAccountEntries.reduce((acc, account) => acc + account.lamports, 0n);\n const refundedLamports = createdAccountEntries.reduce(\n (acc, account) => acc + (account.refunded ? account.lamports : 0n),\n 0n,\n );\n const rentFeeNet = rentLamportsUpfront - refundedLamports;\n\n return {\n createdAccounts: createdAccountEntries,\n rentFee: rentLamportsUpfront,\n rentFeeNet,\n refundable: refundedLamports,\n };\n}\n\n/**\n * Resolves an account address used by a compiled instruction.\n *\n * Compiled instructions reference accounts by index into the transaction\n * message's `staticAccounts` list. This helper converts an instruction-local\n * `accountIndex` into the corresponding static account address.\n *\n * Returns `undefined` when the instruction does not provide an account at the\n * requested index.\n */\nfunction _getInstructionAccountAddress(\n message: CompiledTransactionMessage,\n instruction: CompiledTransactionMessage['instructions'][number],\n accountIndex: number,\n): CompiledTransactionMessage['staticAccounts'][number] | undefined {\n const messageAccountIndex = instruction.accountIndices?.[accountIndex];\n\n if (messageAccountIndex === undefined) {\n return undefined;\n }\n\n return message.staticAccounts[messageAccountIndex];\n}\n\n/**\n * Extracts lamports from System Program account-creation instruction data.\n *\n * Supported instruction layouts:\n * - `CreateAccount` (`u32 discriminator = 0`): lamports at byte offset `4`.\n * - `CreateAccountWithSeed` (`u32 discriminator = 3`): lamports are located\n * after the variable-length seed field.\n *\n * Returns `0n` when data is missing, malformed, or not one of the supported\n * System Program create-account variants.\n */\nfunction _getSystemCreateAccountLamports(data: CompiledTransactionMessage['instructions'][number]['data']): bigint {\n if (!data || data.length < 12) {\n return 0n;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const instructionDiscriminator = view.getUint32(0, true);\n\n if (instructionDiscriminator === 0 && data.length >= 12) {\n return view.getBigUint64(4, true);\n }\n\n if (instructionDiscriminator === 3 && data.length >= 44) {\n const seedLenOffset = 36;\n const seedLength = Number(view.getBigUint64(seedLenOffset, true));\n if (!Number.isSafeInteger(seedLength) || seedLength < 0) {\n return 0n;\n }\n\n const lamportsOffset = seedLenOffset + 8 + seedLength;\n if (lamportsOffset + 8 > data.length) {\n return 0n;\n }\n\n return new DataView(data.buffer, data.byteOffset + lamportsOffset, 8).getBigUint64(0, true);\n }\n\n return 0n;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"estimate-native-fee.js","names":["isEvmAddress","isSolanaAddress"],"sources":["../../../../src/transfer-service/markr/_handlers/estimate-native-fee.ts"],"sourcesContent":["import {\n type CompiledTransactionMessage,\n getCompiledTransactionMessageDecoder,\n isAddress as isSolanaAddress,\n type Rpc,\n type SolanaRpcApi,\n type TransactionMessageBytes,\n type TransactionMessageBytesBase64,\n} from '@solana/kit';\nimport { encodeFunctionData, erc20Abi, type Address as EvmAddress, isAddress as isEvmAddress } from 'viem';\nimport { ErrorCode, ErrorReason, InvalidParamsError, SdkError } from '../../../errors';\nimport type { EstimateNativeFeeOptions, NativeFeeEstimate, TransferService } from '../../../types/service';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, getSolanaRpcForChain } from '../../_utils';\nimport { estimateEvmFeesPerGas } from '../../_evm-gas';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport {\n assetToAddressString,\n calculateMarkrMinimumAmountOut,\n decodeMarkrRevertError,\n getMarkrSwapWrapperAbi,\n isTokenAddressNative,\n} from '../_utils';\nimport type { WrappedSwapTransactionResponse } from '../_schema';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { caip2ToEip155ChainId } from '../../../utils/caip';\nimport type { Quote } from '../../../types/quote';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { decodeSolanaTransactionBase64, refreshSolanaSwapTransactionBlockhash } from '../_solana-utils';\n\n/**\n * This is just a fallback value used in the case that an\n * allowance approval is needed, and Markr didn't return us any gas estimate data.\n *\n * Just based on my review of some quotes, I think this is a reasonable estimate\n * left on the higher end.\n *\n * Assume this could need to change.\n */\nconst EVM_SWAP_FALLBACK_GAS_ESTIMATE = 700_000n;\n\n/**\n * Compute Budget program address.\n *\n * Used to detect `SetComputeUnitPrice` instructions when deriving\n * the Solana priority fee component from transaction message bytes.\n */\nconst SOLANA_COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';\n\n/**\n * System Program address.\n *\n * Used to detect account-creation instructions and extract lamports\n * funded upfront for newly created system accounts.\n */\nconst SOLANA_SYSTEM_PROGRAM = '11111111111111111111111111111111';\n\n/**\n * Associated Token Account (ATA) program address.\n *\n * Used to detect ATA create instructions so the estimator can include\n * rent-exempt funding when the ATA does not already exist on-chain.\n */\nconst SOLANA_ASSOCIATED_TOKEN_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';\n\n/**\n * Legacy SPL Token program address.\n *\n * Used to detect close-account instructions that may refund lamports\n * from temporary token accounts back to the sender.\n */\nconst SOLANA_SPL_TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\n\n/**\n * SPL Token-2022 program address.\n *\n * Used alongside the legacy token program for close-account detection\n * when computing expected refundable lamports.\n */\nconst SOLANA_SPL_TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';\n\n/**\n * SPL token account size for rent-exemption calculations.\n *\n * Used with `getMinimumBalanceForRentExemption` to estimate ATA\n * creation funding requirements.\n */\nconst SPL_TOKEN_ACCOUNT_SIZE_BYTES = 165n;\n\nexport interface EstimateNativeFeeFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n}\n\ninterface CreatedRentAccount {\n address: string;\n kind: 'ata' | 'system';\n lamports: bigint;\n refunded: boolean;\n}\n\ninterface SolanaRentEstimate {\n createdAccounts: CreatedRentAccount[];\n rentFee: bigint;\n rentFeeNet: bigint;\n refundable: bigint;\n}\n\nexport function estimateNativeFeeFactory(config: EstimateNativeFeeFactoryConfig): TransferService['estimateNativeFee'] {\n return async (quote, options) => {\n // Either the source chain is EVM or Solana, we need to call different functions that\n // calculate the native fee depending on the chain kind.\n\n if (isEvmNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeEvm(quote, options, config);\n }\n\n if (isSolanaNamespace(quote.sourceChain.chainId)) {\n return await _estimateNativeFeeSolana(quote, options, config);\n }\n\n throw new InvalidParamsError(\n ErrorReason.INVALID_PARAMS,\n `Unsupported source chain namespace for estimateNativeFee: ${quote.sourceChain.chainId}`,\n );\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeEvm(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n if (!isEvmAddress(quote.fromAddress)) {\n throw new InvalidParamsError(ErrorReason.INVALID_PARAMS, `Invalid fromAddress: ${quote.fromAddress}`);\n }\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isEvmAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid EVM address. Can not call estimateGas.`,\n });\n }\n\n const isAssetInNative = isTokenAddressNative(assetInAddressString);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n let allowanceApprovalGas = 0n;\n if (!isAssetInNative) {\n // Check if approval is needed, and if so, calculate the gas cost for the approval.\n let allowance: bigint;\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n try {\n allowance = await sourceClient.readContract({\n address: assetInAddressString,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [quote.fromAddress, spenderAddress],\n });\n } catch (error) {\n throw new SdkError('Error during allowance check', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to read ERC20 allowance for Markr spender.',\n });\n }\n\n const approvalNeeded = allowance < quote.amountIn;\n\n if (approvalNeeded) {\n try {\n allowanceApprovalGas = await sourceClient.estimateGas({\n account: quote.fromAddress,\n to: assetInAddressString,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n }),\n value: 0n,\n });\n } catch (error) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: error,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n }\n }\n\n const allowanceApprovalIsNeeded = allowanceApprovalGas > 0n;\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Received non-EVM swap response from Markr. Expected EVM transaction data for gas estimation, but got response with chainType ${swap.chainType}.`,\n });\n }\n\n let gasWithMargin = 0n;\n\n // If an approval is needed, we can't call `eth_estimateGas` on the\n // swap transaction because no actual allowance approval was performed yet,\n // so a gas estimation would fail due to needing allowance.\n //\n // So if an allowance approval is needed, we fall back to some other logic for\n // estimating the swap tx gas.\n if (allowanceApprovalIsNeeded) {\n // Attempt to use the Markr provided `gasEstimate` first if available.\n // Otherwise we attempt to get the gas from the `fees` component that are applicable\n // to the source chain. If neither of those are available, we fall back to a hardcoded value.\n if (quote.gasEstimate) {\n gasWithMargin = applyFeeUnitsBpsMargin(quote.gasEstimate, options?.feeUnitsMarginBps);\n } else {\n const sourceGasFee = quote.fees\n .filter((fee) => fee.type === 'gas' && fee.chainId === quote.sourceChain.chainId)\n .reduce((acc, fee) => acc + fee.amount, 0n);\n\n gasWithMargin = applyFeeUnitsBpsMargin(\n sourceGasFee || EVM_SWAP_FALLBACK_GAS_ESTIMATE,\n options?.feeUnitsMarginBps,\n );\n }\n } else {\n gasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n feeUnitsMarginBps: options?.feeUnitsMarginBps,\n fromAddress: quote.fromAddress,\n sourceClient,\n swap,\n });\n }\n\n const fees = await estimateEvmFeesPerGas(sourceClient, quote.sourceChain, options?.overrides?.feeRateTier);\n\n const maxFeePerGas = options?.overrides?.maxFeePerGas ?? fees.maxFeePerGas;\n const maxPriorityFeePerGas = options?.overrides?.maxPriorityFeePerGas ?? fees.maxPriorityFeePerGas;\n\n const totalFee =\n (gasWithMargin + applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps)) * maxFeePerGas;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee,\n meta: {\n approvalFee: allowanceApprovalIsNeeded\n ? applyFeeUnitsBpsMargin(allowanceApprovalGas, options?.feeUnitsMarginBps) * maxFeePerGas\n : undefined,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateNativeFeeSolana(\n quote: Quote,\n options: EstimateNativeFeeOptions | undefined,\n { apiOptions, appId }: EstimateNativeFeeFactoryConfig,\n): Promise<NativeFeeEstimate> {\n const assetInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n\n if (!isSolanaAddress(assetInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: `assetIn address is not a valid Solana address. Can not call estimateGas.`,\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: assetInAddressString,\n tokenOut: assetToAddressString(quote.assetOut, quote.targetChain.chainId),\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n // Should hopefully never happen.\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-Solana swap response from Markr. Expected Solana transaction data for fee estimation.',\n });\n }\n\n const rpc = getSolanaRpcForChain({ chain: quote.sourceChain });\n const refreshedSwapTransactionBase64 = await refreshSolanaSwapTransactionBlockhash(swap.swapTransaction, rpc);\n const { messageBytes: transactionMessageBytes } = decodeSolanaTransactionBase64(refreshedSwapTransactionBase64);\n const transactionMessageBytesBase64 = _transactionMessageBytesToBase64(transactionMessageBytes);\n\n const initialFeeForMessageResponse = await rpc.getFeeForMessage(transactionMessageBytesBase64).send();\n\n const baseFeeLamports = initialFeeForMessageResponse.value;\n\n if (!baseFeeLamports) {\n throw new SdkError('Failed to get fee for message after refreshing blockhash.', ErrorCode.TIMEOUT, {\n details: 'Unable to estimate Solana base fee because the transaction message remained invalid.',\n });\n }\n\n const sim = await rpc\n .simulateTransaction(refreshedSwapTransactionBase64, {\n encoding: 'base64',\n sigVerify: false,\n replaceRecentBlockhash: true, // Helps prevent block hash expiration issues during simulation.\n })\n .send();\n\n const unitsConsumed = applyFeeUnitsBpsMargin(sim.value.unitsConsumed ?? 0n, options?.feeUnitsMarginBps);\n // Lamports\n const computeUnitPriceMicroLamports = _getComputeUnitPriceMicroLamports(transactionMessageBytes);\n const priorityFee = (unitsConsumed * computeUnitPriceMicroLamports) / 1_000_000n;\n const rentEstimate = await _estimateRentFeesFromTransactionMessage({\n fromAddress: quote.fromAddress,\n rpc,\n transactionMessageBytes,\n });\n const totalLamports = baseFeeLamports + priorityFee + rentEstimate.rentFee;\n\n return {\n asset: quote.sourceChain.networkToken,\n totalFee: totalLamports,\n refundable: rentEstimate.refundable,\n meta: {\n baseFee: baseFeeLamports,\n priorityFee,\n computeUnitPriceMicroLamports,\n unitsConsumed,\n createdAccounts: rentEstimate.createdAccounts,\n rentFee: rentEstimate.rentFee,\n rentFeeNet: rentEstimate.rentFeeNet,\n },\n };\n}\n\n/** @internal */\nexport async function _estimateGasFromSwapResponse({\n crossChain,\n feeUnitsMarginBps,\n fromAddress,\n sourceClient,\n swap,\n}: {\n crossChain: boolean;\n feeUnitsMarginBps?: number;\n fromAddress: EvmAddress;\n sourceClient: ReturnType<typeof getEvmClientForChain>;\n swap: WrappedSwapTransactionResponse;\n}): Promise<bigint> {\n try {\n const gasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: swap.to,\n data: swap.data,\n value: swap.value,\n });\n\n return applyFeeUnitsBpsMargin(gasEstimate, feeUnitsMarginBps);\n } catch (err) {\n let details = 'Failed to estimate gas for Markr swap transaction.';\n\n try {\n const markrSwapWrapperAbi = await getMarkrSwapWrapperAbi(crossChain);\n const decodedRevert = decodeMarkrRevertError(markrSwapWrapperAbi, err);\n\n if (decodedRevert) {\n details = `${details} Markr revert: ${decodedRevert}.`;\n }\n } catch {\n // Keep the base details message if revert decoding fails.\n }\n\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details,\n });\n }\n}\n\nfunction _transactionMessageBytesToBase64(bytes: TransactionMessageBytes): TransactionMessageBytesBase64 {\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary) as TransactionMessageBytesBase64;\n}\n\nfunction _getComputeUnitPriceMicroLamports(transactionMessageBytes: TransactionMessageBytes): bigint {\n const compiledTxMessage = getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n for (const instruction of compiledTxMessage.instructions) {\n const programAddress = compiledTxMessage.staticAccounts[instruction.programAddressIndex];\n if (programAddress !== SOLANA_COMPUTE_BUDGET_PROGRAM) {\n continue;\n }\n\n const data = instruction.data;\n\n if (!data || data.length < 1 + 8) {\n continue;\n }\n\n // ComputeBudget: u8 discriminator, then args\n // discriminator 3 = SetComputeUnitPrice, args = u64 microLamports\n const discriminator = data[0];\n if (discriminator !== 3) {\n continue;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n return view.getBigUint64(1, true);\n }\n\n return 0n;\n}\n\nasync function _estimateRentFeesFromTransactionMessage({\n fromAddress,\n rpc,\n transactionMessageBytes,\n}: {\n fromAddress: string;\n rpc: Rpc<SolanaRpcApi>;\n transactionMessageBytes: TransactionMessageBytes;\n}): Promise<SolanaRentEstimate> {\n const compiledMessage: CompiledTransactionMessage =\n getCompiledTransactionMessageDecoder().decode(transactionMessageBytes);\n\n const createdAccounts = new Map<string, Omit<CreatedRentAccount, 'refunded'>>();\n const closedAccountDestinationByAddress = new Map<string, string>();\n\n let ataRentLamports: bigint | undefined;\n\n for (const instruction of compiledMessage.instructions) {\n const programAddress = compiledMessage.staticAccounts[instruction.programAddressIndex];\n\n if (programAddress === SOLANA_SYSTEM_PROGRAM) {\n const createdAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n const lamports = _getSystemCreateAccountLamports(instruction.data);\n if (createdAccountAddress && lamports > 0n) {\n createdAccounts.set(createdAccountAddress, {\n address: createdAccountAddress,\n kind: 'system',\n lamports,\n });\n }\n continue;\n }\n\n if (programAddress === SOLANA_ASSOCIATED_TOKEN_PROGRAM) {\n const discriminator = instruction.data?.[0];\n const isAssociatedTokenCreateInstruction =\n discriminator === undefined || discriminator === 0 || discriminator === 1;\n\n if (!isAssociatedTokenCreateInstruction) {\n continue;\n }\n\n const associatedTokenAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n if (!associatedTokenAddress) {\n continue;\n }\n\n const accountInfo = await rpc.getAccountInfo(associatedTokenAddress).send();\n if (accountInfo.value) {\n continue;\n }\n\n if (ataRentLamports === undefined) {\n ataRentLamports = await rpc.getMinimumBalanceForRentExemption(SPL_TOKEN_ACCOUNT_SIZE_BYTES).send();\n }\n\n createdAccounts.set(associatedTokenAddress, {\n address: associatedTokenAddress,\n kind: 'ata',\n lamports: ataRentLamports,\n });\n\n continue;\n }\n\n const isSplTokenProgram =\n programAddress === SOLANA_SPL_TOKEN_PROGRAM || programAddress === SOLANA_SPL_TOKEN_2022_PROGRAM;\n if (!isSplTokenProgram) {\n continue;\n }\n\n const discriminator = instruction.data?.[0];\n if (discriminator !== 9) {\n continue;\n }\n\n const closedAccountAddress = _getInstructionAccountAddress(compiledMessage, instruction, 0);\n const refundDestinationAddress = _getInstructionAccountAddress(compiledMessage, instruction, 1);\n\n if (closedAccountAddress && refundDestinationAddress) {\n closedAccountDestinationByAddress.set(closedAccountAddress, refundDestinationAddress);\n }\n }\n\n const normalizedFromAddress = isSolanaAddress(fromAddress) ? fromAddress : undefined;\n const createdAccountEntries = Array.from(createdAccounts.values()).map((createdAccount) => {\n const refundDestinationAddress = closedAccountDestinationByAddress.get(createdAccount.address);\n const refunded = normalizedFromAddress !== undefined && refundDestinationAddress === normalizedFromAddress;\n\n return {\n ...createdAccount,\n refunded,\n } satisfies CreatedRentAccount;\n });\n\n const rentLamportsUpfront = createdAccountEntries.reduce((acc, account) => acc + account.lamports, 0n);\n const refundedLamports = createdAccountEntries.reduce(\n (acc, account) => acc + (account.refunded ? account.lamports : 0n),\n 0n,\n );\n const rentFeeNet = rentLamportsUpfront - refundedLamports;\n\n return {\n createdAccounts: createdAccountEntries,\n rentFee: rentLamportsUpfront,\n rentFeeNet,\n refundable: refundedLamports,\n };\n}\n\n/**\n * Resolves an account address used by a compiled instruction.\n *\n * Compiled instructions reference accounts by index into the transaction\n * message's `staticAccounts` list. This helper converts an instruction-local\n * `accountIndex` into the corresponding static account address.\n *\n * Returns `undefined` when the instruction does not provide an account at the\n * requested index.\n */\nfunction _getInstructionAccountAddress(\n message: CompiledTransactionMessage,\n instruction: CompiledTransactionMessage['instructions'][number],\n accountIndex: number,\n): CompiledTransactionMessage['staticAccounts'][number] | undefined {\n const messageAccountIndex = instruction.accountIndices?.[accountIndex];\n\n if (messageAccountIndex === undefined) {\n return undefined;\n }\n\n return message.staticAccounts[messageAccountIndex];\n}\n\n/**\n * Extracts lamports from System Program account-creation instruction data.\n *\n * Supported instruction layouts:\n * - `CreateAccount` (`u32 discriminator = 0`): lamports at byte offset `4`.\n * - `CreateAccountWithSeed` (`u32 discriminator = 3`): lamports are located\n * after the variable-length seed field.\n *\n * Returns `0n` when data is missing, malformed, or not one of the supported\n * System Program create-account variants.\n */\nfunction _getSystemCreateAccountLamports(data: CompiledTransactionMessage['instructions'][number]['data']): bigint {\n if (!data || data.length < 12) {\n return 0n;\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const instructionDiscriminator = view.getUint32(0, true);\n\n if (instructionDiscriminator === 0 && data.length >= 12) {\n return view.getBigUint64(4, true);\n }\n\n if (instructionDiscriminator === 3 && data.length >= 44) {\n const seedLenOffset = 36;\n const seedLength = Number(view.getBigUint64(seedLenOffset, true));\n if (!Number.isSafeInteger(seedLength) || seedLength < 0) {\n return 0n;\n }\n\n const lamportsOffset = seedLenOffset + 8 + seedLength;\n if (lamportsOffset + 8 > data.length) {\n return 0n;\n }\n\n return new DataView(data.buffer, data.byteOffset + lamportsOffset, 8).getBigUint64(0, true);\n }\n\n return 0n;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../constants.cjs`),t=require(`../../../errors.cjs`),n=require(`../../../utils/caip.cjs`),r=require(`../../../_utils/chain.cjs`),i=require(`../../_utils.cjs`),a=require(`../_api.cjs`),o=require(`../../../utils/sol-address.cjs`),s=require(`../_utils.cjs`),c=require(`../_type-guards.cjs`),l=require(`./estimate-native-fee.cjs`),
|
|
1
|
+
require(`../../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../../constants.cjs`),t=require(`../../../errors.cjs`),n=require(`../../../utils/caip.cjs`),r=require(`../../../_utils/chain.cjs`),i=require(`../../_utils.cjs`),a=require(`../_api.cjs`),o=require(`../../../utils/sol-address.cjs`),s=require(`../_utils.cjs`),c=require(`../_type-guards.cjs`),l=require(`../_solana-utils.cjs`),u=require(`./estimate-native-fee.cjs`),d=require(`../constants.cjs`);let f=require(`viem`);function p({apiOptions:n,appId:i,environment:a,evmSigner:o,solanaSigner:s}){return async({quote:c,gasSettings:l,fallbackToDefaultOnBatchFailure:u,onStepChange:d})=>{if(c.serviceType!==e.ServiceType.MARKR)throw new t.SdkError(t.ErrorReason.INCORRECT_PROVIDER_PROVIDED,t.ErrorCode.INVALID_PARAMS);let f=Math.floor(Date.now()/1e3);if(c.expiresAt<=f)throw new t.SdkError(t.ErrorReason.QUOTE_EXPIRED,t.ErrorCode.INVALID_PARAMS);let p=c.sourceChain.chainId;if(r.isSolanaNamespace(p))return h({apiOptions:n,appId:i,environment:a,solanaSigner:s,quote:c,onStepChange:d});if(r.isEvmNamespace(p))return m({apiOptions:n,appId:i,environment:a,evmSigner:o,quote:c,gasSettings:l,fallbackToDefaultOnBatchFailure:u,onStepChange:d});throw new t.SdkError(t.ErrorReason.CHAIN_NOT_SUPPORTED,t.ErrorCode.INVALID_PARAMS,{details:`Unsupported source chain namespace: ${p}`})}}async function m({apiOptions:r,appId:o,environment:l,evmSigner:d,quote:p,gasSettings:m,fallbackToDefaultOnBatchFailure:h,onStepChange:_}){let v=p.fromAddress;if(!(0,f.isAddress)(v))throw new t.SdkError(t.ErrorReason.INVALID_PARAMS,t.ErrorCode.INVALID_PARAMS,{details:`fromAddress is not a valid EVM address.`});let y=s.calculateMarkrMinimumAmountOut({amountOut:p.amountOut,assetOut:p.assetOut,slippageBps:p.slippageBps}),b=i.getEvmClientForChain({chain:p.sourceChain}),x=s.assetToAddressString(p.assetIn,p.sourceChain.chainId),S=s.assetToAddressString(p.assetOut,p.targetChain.chainId);if(!(0,f.isAddress)(x))throw new t.SdkError(t.ErrorReason.INVALID_PARAMS,t.ErrorCode.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address.`});let C=x,w=s.isTokenAddressNative(C),T=p.sourceChain.chainId.toLowerCase()!==p.targetChain.chainId.toLowerCase(),{address:E}=await a.markrGetSpenderAddress(r,{chainId:n.caip2ToEip155ChainId(p.sourceChain.chainId),crossChainSwap:T,quoteId:p.id});if(!w&&!E)throw new t.SdkError(t.ErrorReason.CHAIN_NOT_SUPPORTED,t.ErrorCode.INVALID_PARAMS,{details:`Missing Markr spender address for source chain ${p.sourceChain.chainId}.`});let D=!1,O;if(!w&&E&&await b.readContract({address:C,abi:f.erc20Abi,functionName:`allowance`,args:[v,E]})<p.amountIn){D=!0;let e=(0,f.encodeFunctionData)({abi:f.erc20Abi,functionName:`approve`,args:[E,p.amountIn]}),r;try{r=i.applyFeeUnitsBpsMargin(await b.estimateGas({account:v,to:C,data:e,value:0n}),m?.estimateGasMarginBps)}catch(e){throw new t.SdkError(`Error during gas estimation`,t.ErrorCode.VIEM_ERROR,{cause:e,details:`Failed to estimate gas for ERC20 approval transaction.`})}O={chainId:n.caip2ToEip155HexChainId(p.sourceChain.chainId),data:e,from:v,gas:r,to:C,value:0n,...m?.maxFeePerGas===void 0?null:{maxFeePerGas:m.maxFeePerGas,maxPriorityFeePerGas:m.maxPriorityFeePerGas}}}let k=g({approvalRequest:O,requiresApprovalSignature:D,signBatch:d.signBatch,sourceChainId:p.sourceChain.chainId,targetChainId:p.targetChain.chainId}),A=await a.markrSwap(r,{amountIn:p.amountIn.toString(),appId:o,minAmountOut:y.toString(),tokenIn:x,tokenOut:S,uuid:p.id});if(!c.isEvmSwapResponse(A))throw new t.SdkError(t.ErrorReason.CHAIN_NOT_SUPPORTED,t.ErrorCode.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr, but only EVM swaps are supported for this source chain.`});let j=(t,n=Date.now())=>{let r=p.sourceChain.chainId===p.targetChain.chainId;return{amountIn:p.amountIn,amountOut:p.amountOut,environment:l,fees:p.fees,fromAddress:p.fromAddress,id:p.id,partnerFeeBps:p.partnerFeeBps,sourceAsset:p.assetIn,sourceChain:p.sourceChain,status:`source-pending`,targetAsset:p.assetOut,targetChain:p.targetChain,toAddress:p.toAddress,type:e.ServiceType.MARKR,source:{confirmationCount:0,requiredConfirmationCount:r?1:2,startedAtMs:n,txHash:t}}};if(k&&O){let r={currentSignature:1,currentSignatureReason:e.TransferSignatureReason.TokensTransfer,quote:p,requiredSignatures:1},i=d.signBatch;if(!i)throw new t.SdkError(`One-click batch signer is not available.`,t.ErrorCode.SIGNING_FAILED);let a={chainId:n.caip2ToEip155HexChainId(p.sourceChain.chainId),data:A.data,from:v,gas:void 0,to:A.to,value:A.value,...m?.maxFeePerGas===void 0?null:{maxFeePerGas:m.maxFeePerGas,maxPriorityFeePerGas:m.maxPriorityFeePerGas}};_?.(r);try{let e=(await i([O,a],async e=>b.sendRawTransaction({serializedTransaction:e}),r)).at(-1);if(!e)throw new t.SdkError(`One-click batch signing returned no transaction hashes.`,t.ErrorCode.SIGNING_FAILED);return j(e)}catch(e){if(!h)throw e}}if(D&&O){let n={currentSignature:1,currentSignatureReason:e.TransferSignatureReason.AllowanceApproval,quote:p,requiredSignatures:2};_?.(n);let r=await d.sign(O,async e=>b.sendRawTransaction({serializedTransaction:e}),n);if((await b.waitForTransactionReceipt({hash:r})).status===`reverted`)return i.makeFailedTransferFromQuote(p,{environment:l,errorCode:t.ErrorCode.TRANSACTION_REVERTED,errorReason:`ERC20 approval transaction was reverted`})}let M=await u._estimateGasFromSwapResponse({crossChain:T,fromAddress:v,feeUnitsMarginBps:m?.estimateGasMarginBps,sourceClient:b,swap:A}),N={chainId:n.caip2ToEip155HexChainId(p.sourceChain.chainId),data:A.data,from:v,gas:M,to:A.to,value:A.value,...m?.maxFeePerGas===void 0?null:{maxFeePerGas:m.maxFeePerGas,maxPriorityFeePerGas:m.maxPriorityFeePerGas}},P={currentSignature:D?2:1,currentSignatureReason:e.TransferSignatureReason.TokensTransfer,quote:p,requiredSignatures:D?2:1};_?.(P);let F=Date.now();return j(await d.sign(N,async e=>b.sendRawTransaction({serializedTransaction:e}),P),F)}async function h({apiOptions:n,appId:r,environment:u,solanaSigner:f,quote:p,onStepChange:m}){if(!f)throw new t.SdkError(t.ErrorReason.INVALID_PARAMS,t.ErrorCode.INVALID_PARAMS,{details:`solanaSigner is required for Solana transfers but was not provided.`});if(!o.isSolAddress(p.fromAddress))throw new t.SdkError(t.ErrorReason.INVALID_PARAMS,t.ErrorCode.INVALID_PARAMS,{details:`fromAddress is not a valid Solana address.`});let h=s.calculateMarkrMinimumAmountOut({amountOut:p.amountOut,assetOut:p.assetOut,slippageBps:p.slippageBps}),g=s.assetToAddressString(p.assetIn,p.sourceChain.chainId),_=s.assetToAddressString(p.assetOut,p.targetChain.chainId),v=await a.markrSwap(n,{amountIn:p.amountIn.toString(),appId:r,minAmountOut:h.toString(),tokenIn:g,tokenOut:_,userPublicKey:p.fromAddress,uuid:p.id});if(!c.isSolanaSwapResponse(v))throw new t.SdkError(t.ErrorReason.CHAIN_NOT_SUPPORTED,t.ErrorCode.INVALID_PARAMS,{details:`Received non-SVM swap response from Markr, but only SVM swaps are supported for this source chain.`});let y=await l.refreshSolanaSwapTransactionBlockhash(v.swapTransaction,i.getSolanaRpcForChain({chain:p.sourceChain})),b={currentSignature:1,currentSignatureReason:e.TransferSignatureReason.TokensTransfer,quote:p,requiredSignatures:1};m?.(b);let x=Date.now(),S=await f.signAndSend({account:p.fromAddress,serializedTx:y},b);return{amountIn:p.amountIn,amountOut:p.amountOut,environment:u,fees:p.fees,fromAddress:p.fromAddress,id:p.id,partnerFeeBps:p.partnerFeeBps,sourceAsset:p.assetIn,sourceChain:p.sourceChain,status:`source-pending`,targetAsset:p.assetOut,targetChain:p.targetChain,toAddress:p.toAddress,type:e.ServiceType.MARKR,source:{confirmationCount:0,requiredConfirmationCount:d.SOLANA_REQUIRED_CONFIRMATIONS,startedAtMs:x,txHash:S}}}function g({approvalRequest:e,requiresApprovalSignature:t,signBatch:n,sourceChainId:i,targetChainId:a}){return r.isEvmNamespace(i)&&r.isEvmNamespace(a)&&i===a&&t&&e!==void 0&&typeof n==`function`}exports.transferAssetFactory=p;
|
|
2
2
|
//# sourceMappingURL=transfer-asset.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer-asset.cjs","names":["ServiceType","SdkError","ErrorReason","ErrorCode","isSolanaNamespace","isEvmNamespace","calculateMarkrMinimumAmountOut","getEvmClientForChain","assetToAddressString","isTokenAddressNative","markrGetSpenderAddress","caip2ToEip155ChainId","erc20Abi","applyFeeUnitsBpsMargin","caip2ToEip155HexChainId","markrSwap","isEvmSwapResponse","TransferSignatureReason","makeFailedTransferFromQuote","_estimateGasFromSwapResponse","isSolAddress","isSolanaSwapResponse","SOLANA_REQUIRED_CONFIRMATIONS"],"sources":["../../../../src/transfer-service/markr/_handlers/transfer-asset.ts"],"sourcesContent":["import { encodeFunctionData, erc20Abi, isAddress } from 'viem';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { ServiceType, TransferSignatureReason } from '../../../constants';\nimport type { Environment } from '../../../constants';\nimport { ErrorCode, ErrorReason, SdkError } from '../../../errors';\nimport type { TransferService } from '../../../types/service';\nimport type { EvmSigner, EvmTransactionRequest, SolanaSigner } from '../../../types/signer';\nimport type { Transfer, TransferStepDetails } from '../../../types/transfer';\nimport { caip2ToEip155ChainId, caip2ToEip155HexChainId } from '../../../utils/caip';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, makeFailedTransferFromQuote } from '../../_utils';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport { assetToAddressString, calculateMarkrMinimumAmountOut, isTokenAddressNative } from '../_utils';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { SOLANA_REQUIRED_CONFIRMATIONS } from '../constants';\nimport { _estimateGasFromSwapResponse } from './estimate-native-fee';\nimport type { Caip2ChainId } from '../../../types/caip';\n\nexport interface TransferAssetFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n solanaSigner?: SolanaSigner;\n}\n\nexport function transferAssetFactory({\n apiOptions,\n appId,\n environment,\n evmSigner,\n solanaSigner,\n}: TransferAssetFactoryConfig): TransferService['transferAsset'] {\n return async ({ quote, gasSettings, fallbackToDefaultOnBatchFailure, onStepChange }) => {\n if (quote.serviceType !== ServiceType.MARKR) {\n throw new SdkError(ErrorReason.INCORRECT_PROVIDER_PROVIDED, ErrorCode.INVALID_PARAMS);\n }\n\n const now = Math.floor(Date.now() / 1_000);\n\n if (quote.expiresAt <= now) {\n throw new SdkError(ErrorReason.QUOTE_EXPIRED, ErrorCode.INVALID_PARAMS);\n }\n\n const sourceChainId = quote.sourceChain.chainId;\n\n if (isSolanaNamespace(sourceChainId)) {\n return _executeSvmTransfer({ apiOptions, appId, environment, solanaSigner, quote, onStepChange });\n }\n\n if (isEvmNamespace(sourceChainId)) {\n return _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n });\n }\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Unsupported source chain namespace: ${sourceChainId}`,\n });\n };\n}\n\n// ---------------------------------------------------------------------------\n// EVM transfer path\n// ---------------------------------------------------------------------------\n\ninterface EvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n gasSettings?: Parameters<TransferService['transferAsset']>[0]['gasSettings'];\n fallbackToDefaultOnBatchFailure?: Parameters<TransferService['transferAsset']>[0]['fallbackToDefaultOnBatchFailure'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n}: EvmTransferParams): Promise<Transfer> {\n const fromAddress = quote.fromAddress;\n\n if (!isAddress(fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid EVM address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n if (!isAddress(tokenInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'assetIn address is not a valid EVM address.',\n });\n }\n\n const tokenInAddress = tokenInAddressString;\n const isTokenInNative = isTokenAddressNative(tokenInAddress);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n if (!isTokenInNative && !spenderAddress) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Missing Markr spender address for source chain ${quote.sourceChain.chainId}.`,\n });\n }\n\n let requiresApprovalSignature = false;\n let approvalRequest: EvmTransactionRequest | undefined;\n\n if (!isTokenInNative && spenderAddress) {\n const allowance = await sourceClient.readContract({\n address: tokenInAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [fromAddress, spenderAddress],\n });\n\n if (allowance < quote.amountIn) {\n requiresApprovalSignature = true;\n\n const approvalData = encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n });\n\n let approvalGasWithMargin: bigint | undefined;\n\n try {\n const approvalGasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: tokenInAddress,\n data: approvalData,\n value: 0n,\n });\n\n approvalGasWithMargin = applyFeeUnitsBpsMargin(approvalGasEstimate, gasSettings?.estimateGasMarginBps);\n } catch (err) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n\n approvalRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: approvalData,\n from: fromAddress,\n gas: approvalGasWithMargin,\n to: tokenInAddress,\n value: 0n,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n }\n }\n\n const maybeOneClickBatch = isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch: evmSigner.signBatch,\n sourceChainId: quote.sourceChain.chainId,\n targetChainId: quote.targetChain.chainId,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-EVM swap response from Markr, but only EVM swaps are supported for this source chain.',\n });\n }\n\n const makePendingTransfer = (txHash: `0x${string}`, startedAtMs: number = Date.now()): Transfer => {\n const isSameChainTransfer = quote.sourceChain.chainId === quote.targetChain.chainId;\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: isSameChainTransfer ? 1 : 2,\n startedAtMs,\n txHash,\n },\n };\n };\n\n if (maybeOneClickBatch && approvalRequest) {\n const batchStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n const signBatch = evmSigner.signBatch;\n\n if (!signBatch) {\n throw new SdkError('One-click batch signer is not available.', ErrorCode.SIGNING_FAILED);\n }\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n // Gas is purposely left undefined here.\n // We can't estimate the gas because we aren't executing the\n // approval tx. It's up to the wallet to correctly handle gas estimation.\n gas: undefined,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n onStepChange?.(batchStep);\n\n try {\n const txHashes = await signBatch(\n [approvalRequest, swapRequest],\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n batchStep,\n );\n\n const swapTxHash = txHashes.at(-1);\n\n if (!swapTxHash) {\n throw new SdkError('One-click batch signing returned no transaction hashes.', ErrorCode.SIGNING_FAILED);\n }\n\n return makePendingTransfer(swapTxHash);\n } catch (error) {\n if (!fallbackToDefaultOnBatchFailure) {\n throw error;\n }\n }\n }\n\n if (requiresApprovalSignature && approvalRequest) {\n const approvalStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.AllowanceApproval,\n quote,\n requiredSignatures: 2,\n };\n\n onStepChange?.(approvalStep);\n\n const approvalTxHash = await evmSigner.sign(\n approvalRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n approvalStep,\n );\n\n const approvalReceipt = await sourceClient.waitForTransactionReceipt({ hash: approvalTxHash });\n\n if (approvalReceipt.status === 'reverted') {\n return makeFailedTransferFromQuote(quote, {\n environment,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'ERC20 approval transaction was reverted',\n });\n }\n }\n\n const swapGasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress,\n feeUnitsMarginBps: gasSettings?.estimateGasMarginBps,\n sourceClient,\n swap,\n });\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n gas: swapGasWithMargin,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n const step: TransferStepDetails = {\n currentSignature: requiresApprovalSignature ? 2 : 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: requiresApprovalSignature ? 2 : 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await evmSigner.sign(\n swapRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n step,\n );\n\n return makePendingTransfer(txHash, startedAtMs);\n}\n\n// ---------------------------------------------------------------------------\n// SVM (Solana) transfer path\n// ---------------------------------------------------------------------------\n\ninterface SvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n solanaSigner?: SolanaSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeSvmTransfer({\n apiOptions,\n appId,\n environment,\n solanaSigner,\n quote,\n onStepChange,\n}: SvmTransferParams): Promise<Transfer> {\n if (!solanaSigner) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'solanaSigner is required for Solana transfers but was not provided.',\n });\n }\n\n if (!isSolAddress(quote.fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid Solana address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-SVM swap response from Markr, but only SVM swaps are supported for this source chain.',\n });\n }\n\n const step: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await solanaSigner.signAndSend(\n {\n account: quote.fromAddress,\n serializedTx: swap.swapTransaction,\n },\n step,\n );\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: SOLANA_REQUIRED_CONFIRMATIONS,\n startedAtMs,\n txHash,\n },\n };\n}\n\n/**\n * Determines whether one-click swap eligibility criteria is met.\n *\n * Only same-chain EVM swaps are currently eligible.\n */\nfunction isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch,\n sourceChainId,\n targetChainId,\n}: {\n approvalRequest?: EvmTransactionRequest;\n requiresApprovalSignature: boolean;\n signBatch?: EvmSigner['signBatch'];\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n}): boolean {\n return (\n isEvmNamespace(sourceChainId) &&\n isEvmNamespace(targetChainId) &&\n sourceChainId === targetChainId &&\n requiresApprovalSignature &&\n approvalRequest !== undefined &&\n typeof signBatch === 'function'\n );\n}\n"],"mappings":"ocA0BA,SAAgB,EAAqB,CACnC,aACA,QACA,cACA,YACA,gBAC+D,CAC/D,OAAO,MAAO,CAAE,QAAO,cAAa,kCAAiC,kBAAmB,CACtF,GAAI,EAAM,cAAgBA,EAAAA,YAAY,MACpC,MAAM,IAAIC,EAAAA,SAASC,EAAAA,YAAY,4BAA6BC,EAAAA,UAAU,eAAe,CAGvF,IAAM,EAAM,KAAK,MAAM,KAAK,KAAK,CAAG,IAAM,CAE1C,GAAI,EAAM,WAAa,EACrB,MAAM,IAAIF,EAAAA,SAASC,EAAAA,YAAY,cAAeC,EAAAA,UAAU,eAAe,CAGzE,IAAM,EAAgB,EAAM,YAAY,QAExC,GAAIC,EAAAA,kBAAkB,EAAc,CAClC,OAAO,EAAoB,CAAE,aAAY,QAAO,cAAa,eAAc,QAAO,eAAc,CAAC,CAGnG,GAAIC,EAAAA,eAAe,EAAc,CAC/B,OAAO,EAAoB,CACzB,aACA,QACA,cACA,YACA,QACA,cACA,kCACA,eACD,CAAC,CAGJ,MAAM,IAAIJ,EAAAA,SAASC,EAAAA,YAAY,oBAAqBC,EAAAA,UAAU,eAAgB,CAC5E,QAAS,uCAAuC,IACjD,CAAC,EAmBN,eAAe,EAAoB,CACjC,aACA,QACA,cACA,YACA,QACA,cACA,kCACA,gBACuC,CACvC,IAAM,EAAc,EAAM,YAE1B,GAAI,EAAA,EAAA,EAAA,WAAW,EAAY,CACzB,MAAM,IAAIF,EAAAA,SAASC,EAAAA,YAAY,eAAgBC,EAAAA,UAAU,eAAgB,CACvE,QAAS,0CACV,CAAC,CAGJ,IAAM,EAAeG,EAAAA,+BAA+B,CAClD,UAAW,EAAM,UACjB,SAAU,EAAM,SAChB,YAAa,EAAM,YACpB,CAAC,CAEI,EAAeC,EAAAA,qBAAqB,CAAE,MAAO,EAAM,YAAa,CAAC,CAEjE,EAAuBC,EAAAA,qBAAqB,EAAM,QAAS,EAAM,YAAY,QAAQ,CACrF,EAAwBA,EAAAA,qBAAqB,EAAM,SAAU,EAAM,YAAY,QAAQ,CAE7F,GAAI,EAAA,EAAA,EAAA,WAAW,EAAqB,CAClC,MAAM,IAAIP,EAAAA,SAASC,EAAAA,YAAY,eAAgBC,EAAAA,UAAU,eAAgB,CACvE,QAAS,8CACV,CAAC,CAGJ,IAAM,EAAiB,EACjB,EAAkBM,EAAAA,qBAAqB,EAAe,CACtD,EAAmB,EAAM,YAAY,QAAQ,aAAa,GAAK,EAAM,YAAY,QAAQ,aAAa,CAEtG,CAAE,QAAS,GAAmB,MAAMC,EAAAA,uBAAuB,EAAY,CAC3E,QAASC,EAAAA,qBAAqB,EAAM,YAAY,QAAQ,CACxD,eAAgB,EAChB,QAAS,EAAM,GAChB,CAAC,CAEF,GAAI,CAAC,GAAmB,CAAC,EACvB,MAAM,IAAIV,EAAAA,SAASC,EAAAA,YAAY,oBAAqBC,EAAAA,UAAU,eAAgB,CAC5E,QAAS,kDAAkD,EAAM,YAAY,QAAQ,GACtF,CAAC,CAGJ,IAAI,EAA4B,GAC5B,EAEJ,GAAI,CAAC,GAAmB,GACJ,MAAM,EAAa,aAAa,CAChD,QAAS,EACT,IAAKS,EAAAA,SACL,aAAc,YACd,KAAM,CAAC,EAAa,EAAe,CACpC,CAAC,CAEc,EAAM,SAAU,CAC9B,EAA4B,GAE5B,IAAM,GAAA,EAAA,EAAA,oBAAkC,CACtC,IAAKA,EAAAA,SACL,aAAc,UACd,KAAM,CAAC,EAAgB,EAAM,SAAS,CACvC,CAAC,CAEE,EAEJ,GAAI,CAQF,EAAwBC,EAAAA,uBAPI,MAAM,EAAa,YAAY,CACzD,QAAS,EACT,GAAI,EACJ,KAAM,EACN,MAAO,GACR,CAAC,CAEkE,GAAa,qBAAqB,OAC/F,EAAK,CACZ,MAAM,IAAIZ,EAAAA,SAAS,8BAA+BE,EAAAA,UAAU,WAAY,CACtE,MAAO,EACP,QAAS,yDACV,CAAC,CAGJ,EAAkB,CAChB,QAASW,EAAAA,wBAAwB,EAAM,YAAY,QAAQ,CAC3D,KAAM,EACN,KAAM,EACN,IAAK,EACL,GAAI,EACJ,MAAO,GACP,GAAI,GAAa,eAAiB,IAAA,GAK9B,KAJA,CACE,aAAc,EAAY,aAC1B,qBAAsB,EAAY,qBACnC,CAEN,CAIL,IAAM,EAAqB,EAAwB,CACjD,kBACA,4BACA,UAAW,EAAU,UACrB,cAAe,EAAM,YAAY,QACjC,cAAe,EAAM,YAAY,QAClC,CAAC,CAEI,EAAO,MAAMC,EAAAA,UAAU,EAAY,CACvC,SAAU,EAAM,SAAS,UAAU,CACnC,QACA,aAAc,EAAa,UAAU,CACrC,QAAS,EACT,SAAU,EACV,KAAM,EAAM,GACb,CAAC,CAEF,GAAI,CAACC,EAAAA,kBAAkB,EAAK,CAC1B,MAAM,IAAIf,EAAAA,SAASC,EAAAA,YAAY,oBAAqBC,EAAAA,UAAU,eAAgB,CAC5E,QAAS,qGACV,CAAC,CAGJ,IAAM,GAAuB,EAAuB,EAAsB,KAAK,KAAK,GAAe,CACjG,IAAM,EAAsB,EAAM,YAAY,UAAY,EAAM,YAAY,QAE5E,MAAO,CACL,SAAU,EAAM,SAChB,UAAW,EAAM,UACjB,cACA,KAAM,EAAM,KACZ,YAAa,EAAM,YACnB,GAAI,EAAM,GACV,cAAe,EAAM,cACrB,YAAa,EAAM,QACnB,YAAa,EAAM,YACnB,OAAQ,iBACR,YAAa,EAAM,SACnB,YAAa,EAAM,YACnB,UAAW,EAAM,UACjB,KAAMH,EAAAA,YAAY,MAClB,OAAQ,CACN,kBAAmB,EACnB,0BAA2B,EAAsB,EAAI,EACrD,cACA,SACD,CACF,EAGH,GAAI,GAAsB,EAAiB,CACzC,IAAM,EAAiC,CACrC,iBAAkB,EAClB,uBAAwBiB,EAAAA,wBAAwB,eAChD,QACA,mBAAoB,EACrB,CAEK,EAAY,EAAU,UAE5B,GAAI,CAAC,EACH,MAAM,IAAIhB,EAAAA,SAAS,2CAA4CE,EAAAA,UAAU,eAAe,CAG1F,IAAM,EAAqC,CACzC,QAASW,EAAAA,wBAAwB,EAAM,YAAY,QAAQ,CAC3D,KAAM,EAAK,KACX,KAAM,EAIN,IAAK,IAAA,GACL,GAAI,EAAK,GACT,MAAO,EAAK,MACZ,GAAI,GAAa,eAAiB,IAAA,GAK9B,KAJA,CACE,aAAc,EAAY,aAC1B,qBAAsB,EAAY,qBACnC,CAEN,CAED,IAAe,EAAU,CAEzB,GAAI,CAOF,IAAM,GANW,MAAM,EACrB,CAAC,EAAiB,EAAY,CAC9B,KAAO,IAAiB,EAAa,mBAAmB,CAAE,sBAAuB,EAAc,CAAC,CAChG,EACD,EAE2B,GAAG,GAAG,CAElC,GAAI,CAAC,EACH,MAAM,IAAIb,EAAAA,SAAS,0DAA2DE,EAAAA,UAAU,eAAe,CAGzG,OAAO,EAAoB,EAAW,OAC/B,EAAO,CACd,GAAI,CAAC,EACH,MAAM,GAKZ,GAAI,GAA6B,EAAiB,CAChD,IAAM,EAAoC,CACxC,iBAAkB,EAClB,uBAAwBc,EAAAA,wBAAwB,kBAChD,QACA,mBAAoB,EACrB,CAED,IAAe,EAAa,CAE5B,IAAM,EAAiB,MAAM,EAAU,KACrC,EACA,KAAO,IAAiB,EAAa,mBAAmB,CAAE,sBAAuB,EAAc,CAAC,CAChG,EACD,CAID,IAFwB,MAAM,EAAa,0BAA0B,CAAE,KAAM,EAAgB,CAAC,EAE1E,SAAW,WAC7B,OAAOC,EAAAA,4BAA4B,EAAO,CACxC,cACA,UAAWf,EAAAA,UAAU,qBACrB,YAAa,0CACd,CAAC,CAIN,IAAM,EAAoB,MAAMgB,EAAAA,6BAA6B,CAC3D,WAAY,EACZ,cACA,kBAAmB,GAAa,qBAChC,eACA,OACD,CAAC,CAEI,EAAqC,CACzC,QAASL,EAAAA,wBAAwB,EAAM,YAAY,QAAQ,CAC3D,KAAM,EAAK,KACX,KAAM,EACN,IAAK,EACL,GAAI,EAAK,GACT,MAAO,EAAK,MACZ,GAAI,GAAa,eAAiB,IAAA,GAK9B,KAJA,CACE,aAAc,EAAY,aAC1B,qBAAsB,EAAY,qBACnC,CAEN,CAEK,EAA4B,CAChC,iBAAkB,EAA4B,EAAI,EAClD,uBAAwBG,EAAAA,wBAAwB,eAChD,QACA,mBAAoB,EAA4B,EAAI,EACrD,CAED,IAAe,EAAK,CAEpB,IAAM,EAAc,KAAK,KAAK,CAQ9B,OAAO,EANQ,MAAM,EAAU,KAC7B,EACA,KAAO,IAAiB,EAAa,mBAAmB,CAAE,sBAAuB,EAAc,CAAC,CAChG,EACD,CAEkC,EAAY,CAgBjD,eAAe,EAAoB,CACjC,aACA,QACA,cACA,eACA,QACA,gBACuC,CACvC,GAAI,CAAC,EACH,MAAM,IAAIhB,EAAAA,SAASC,EAAAA,YAAY,eAAgBC,EAAAA,UAAU,eAAgB,CACvE,QAAS,sEACV,CAAC,CAGJ,GAAI,CAACiB,EAAAA,aAAa,EAAM,YAAY,CAClC,MAAM,IAAInB,EAAAA,SAASC,EAAAA,YAAY,eAAgBC,EAAAA,UAAU,eAAgB,CACvE,QAAS,6CACV,CAAC,CAGJ,IAAM,EAAeG,EAAAA,+BAA+B,CAClD,UAAW,EAAM,UACjB,SAAU,EAAM,SAChB,YAAa,EAAM,YACpB,CAAC,CAEI,EAAuBE,EAAAA,qBAAqB,EAAM,QAAS,EAAM,YAAY,QAAQ,CACrF,EAAwBA,EAAAA,qBAAqB,EAAM,SAAU,EAAM,YAAY,QAAQ,CAEvF,EAAO,MAAMO,EAAAA,UAAU,EAAY,CACvC,SAAU,EAAM,SAAS,UAAU,CACnC,QACA,aAAc,EAAa,UAAU,CACrC,QAAS,EACT,SAAU,EACV,cAAe,EAAM,YACrB,KAAM,EAAM,GACb,CAAC,CAEF,GAAI,CAACM,EAAAA,qBAAqB,EAAK,CAC7B,MAAM,IAAIpB,EAAAA,SAASC,EAAAA,YAAY,oBAAqBC,EAAAA,UAAU,eAAgB,CAC5E,QAAS,qGACV,CAAC,CAGJ,IAAM,EAA4B,CAChC,iBAAkB,EAClB,uBAAwBc,EAAAA,wBAAwB,eAChD,QACA,mBAAoB,EACrB,CAED,IAAe,EAAK,CAEpB,IAAM,EAAc,KAAK,KAAK,CAExB,EAAS,MAAM,EAAa,YAChC,CACE,QAAS,EAAM,YACf,aAAc,EAAK,gBACpB,CACD,EACD,CAED,MAAO,CACL,SAAU,EAAM,SAChB,UAAW,EAAM,UACjB,cACA,KAAM,EAAM,KACZ,YAAa,EAAM,YACnB,GAAI,EAAM,GACV,cAAe,EAAM,cACrB,YAAa,EAAM,QACnB,YAAa,EAAM,YACnB,OAAQ,iBACR,YAAa,EAAM,SACnB,YAAa,EAAM,YACnB,UAAW,EAAM,UACjB,KAAMjB,EAAAA,YAAY,MAClB,OAAQ,CACN,kBAAmB,EACnB,0BAA2BsB,EAAAA,8BAC3B,cACA,SACD,CACF,CAQH,SAAS,EAAwB,CAC/B,kBACA,4BACA,YACA,gBACA,iBAOU,CACV,OACEjB,EAAAA,eAAe,EAAc,EAC7BA,EAAAA,eAAe,EAAc,EAC7B,IAAkB,GAClB,GACA,IAAoB,IAAA,IACpB,OAAO,GAAc"}
|
|
1
|
+
{"version":3,"file":"transfer-asset.cjs","names":["ServiceType","SdkError","ErrorReason","ErrorCode","isSolanaNamespace","isEvmNamespace","calculateMarkrMinimumAmountOut","getEvmClientForChain","assetToAddressString","isTokenAddressNative","markrGetSpenderAddress","caip2ToEip155ChainId","erc20Abi","applyFeeUnitsBpsMargin","caip2ToEip155HexChainId","markrSwap","isEvmSwapResponse","TransferSignatureReason","makeFailedTransferFromQuote","_estimateGasFromSwapResponse","isSolAddress","isSolanaSwapResponse","refreshSolanaSwapTransactionBlockhash","getSolanaRpcForChain","SOLANA_REQUIRED_CONFIRMATIONS"],"sources":["../../../../src/transfer-service/markr/_handlers/transfer-asset.ts"],"sourcesContent":["import { encodeFunctionData, erc20Abi, isAddress } from 'viem';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { ServiceType, TransferSignatureReason } from '../../../constants';\nimport type { Environment } from '../../../constants';\nimport { ErrorCode, ErrorReason, SdkError } from '../../../errors';\nimport type { TransferService } from '../../../types/service';\nimport type { EvmSigner, EvmTransactionRequest, SolanaSigner } from '../../../types/signer';\nimport type { Transfer, TransferStepDetails } from '../../../types/transfer';\nimport { caip2ToEip155ChainId, caip2ToEip155HexChainId } from '../../../utils/caip';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport {\n applyFeeUnitsBpsMargin,\n getEvmClientForChain,\n getSolanaRpcForChain,\n makeFailedTransferFromQuote,\n} from '../../_utils';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport { assetToAddressString, calculateMarkrMinimumAmountOut, isTokenAddressNative } from '../_utils';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { SOLANA_REQUIRED_CONFIRMATIONS } from '../constants';\nimport { _estimateGasFromSwapResponse } from './estimate-native-fee';\nimport type { Caip2ChainId } from '../../../types/caip';\nimport { refreshSolanaSwapTransactionBlockhash } from '../_solana-utils';\n\nexport interface TransferAssetFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n solanaSigner?: SolanaSigner;\n}\n\nexport function transferAssetFactory({\n apiOptions,\n appId,\n environment,\n evmSigner,\n solanaSigner,\n}: TransferAssetFactoryConfig): TransferService['transferAsset'] {\n return async ({ quote, gasSettings, fallbackToDefaultOnBatchFailure, onStepChange }) => {\n if (quote.serviceType !== ServiceType.MARKR) {\n throw new SdkError(ErrorReason.INCORRECT_PROVIDER_PROVIDED, ErrorCode.INVALID_PARAMS);\n }\n\n const now = Math.floor(Date.now() / 1_000);\n\n if (quote.expiresAt <= now) {\n throw new SdkError(ErrorReason.QUOTE_EXPIRED, ErrorCode.INVALID_PARAMS);\n }\n\n const sourceChainId = quote.sourceChain.chainId;\n\n if (isSolanaNamespace(sourceChainId)) {\n return _executeSvmTransfer({ apiOptions, appId, environment, solanaSigner, quote, onStepChange });\n }\n\n if (isEvmNamespace(sourceChainId)) {\n return _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n });\n }\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Unsupported source chain namespace: ${sourceChainId}`,\n });\n };\n}\n\n// ---------------------------------------------------------------------------\n// EVM transfer path\n// ---------------------------------------------------------------------------\n\ninterface EvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n gasSettings?: Parameters<TransferService['transferAsset']>[0]['gasSettings'];\n fallbackToDefaultOnBatchFailure?: Parameters<TransferService['transferAsset']>[0]['fallbackToDefaultOnBatchFailure'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n}: EvmTransferParams): Promise<Transfer> {\n const fromAddress = quote.fromAddress;\n\n if (!isAddress(fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid EVM address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n if (!isAddress(tokenInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'assetIn address is not a valid EVM address.',\n });\n }\n\n const tokenInAddress = tokenInAddressString;\n const isTokenInNative = isTokenAddressNative(tokenInAddress);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n if (!isTokenInNative && !spenderAddress) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Missing Markr spender address for source chain ${quote.sourceChain.chainId}.`,\n });\n }\n\n let requiresApprovalSignature = false;\n let approvalRequest: EvmTransactionRequest | undefined;\n\n if (!isTokenInNative && spenderAddress) {\n const allowance = await sourceClient.readContract({\n address: tokenInAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [fromAddress, spenderAddress],\n });\n\n if (allowance < quote.amountIn) {\n requiresApprovalSignature = true;\n\n const approvalData = encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n });\n\n let approvalGasWithMargin: bigint | undefined;\n\n try {\n const approvalGasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: tokenInAddress,\n data: approvalData,\n value: 0n,\n });\n\n approvalGasWithMargin = applyFeeUnitsBpsMargin(approvalGasEstimate, gasSettings?.estimateGasMarginBps);\n } catch (err) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n\n approvalRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: approvalData,\n from: fromAddress,\n gas: approvalGasWithMargin,\n to: tokenInAddress,\n value: 0n,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n }\n }\n\n const maybeOneClickBatch = isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch: evmSigner.signBatch,\n sourceChainId: quote.sourceChain.chainId,\n targetChainId: quote.targetChain.chainId,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-EVM swap response from Markr, but only EVM swaps are supported for this source chain.',\n });\n }\n\n const makePendingTransfer = (txHash: `0x${string}`, startedAtMs: number = Date.now()): Transfer => {\n const isSameChainTransfer = quote.sourceChain.chainId === quote.targetChain.chainId;\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: isSameChainTransfer ? 1 : 2,\n startedAtMs,\n txHash,\n },\n };\n };\n\n if (maybeOneClickBatch && approvalRequest) {\n const batchStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n const signBatch = evmSigner.signBatch;\n\n if (!signBatch) {\n throw new SdkError('One-click batch signer is not available.', ErrorCode.SIGNING_FAILED);\n }\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n // Gas is purposely left undefined here.\n // We can't estimate the gas because we aren't executing the\n // approval tx. It's up to the wallet to correctly handle gas estimation.\n gas: undefined,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n onStepChange?.(batchStep);\n\n try {\n const txHashes = await signBatch(\n [approvalRequest, swapRequest],\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n batchStep,\n );\n\n const swapTxHash = txHashes.at(-1);\n\n if (!swapTxHash) {\n throw new SdkError('One-click batch signing returned no transaction hashes.', ErrorCode.SIGNING_FAILED);\n }\n\n return makePendingTransfer(swapTxHash);\n } catch (error) {\n if (!fallbackToDefaultOnBatchFailure) {\n throw error;\n }\n }\n }\n\n if (requiresApprovalSignature && approvalRequest) {\n const approvalStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.AllowanceApproval,\n quote,\n requiredSignatures: 2,\n };\n\n onStepChange?.(approvalStep);\n\n const approvalTxHash = await evmSigner.sign(\n approvalRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n approvalStep,\n );\n\n const approvalReceipt = await sourceClient.waitForTransactionReceipt({ hash: approvalTxHash });\n\n if (approvalReceipt.status === 'reverted') {\n return makeFailedTransferFromQuote(quote, {\n environment,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'ERC20 approval transaction was reverted',\n });\n }\n }\n\n const swapGasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress,\n feeUnitsMarginBps: gasSettings?.estimateGasMarginBps,\n sourceClient,\n swap,\n });\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n gas: swapGasWithMargin,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n const step: TransferStepDetails = {\n currentSignature: requiresApprovalSignature ? 2 : 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: requiresApprovalSignature ? 2 : 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await evmSigner.sign(\n swapRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n step,\n );\n\n return makePendingTransfer(txHash, startedAtMs);\n}\n\n// ---------------------------------------------------------------------------\n// SVM (Solana) transfer path\n// ---------------------------------------------------------------------------\n\ninterface SvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n solanaSigner?: SolanaSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeSvmTransfer({\n apiOptions,\n appId,\n environment,\n solanaSigner,\n quote,\n onStepChange,\n}: SvmTransferParams): Promise<Transfer> {\n if (!solanaSigner) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'solanaSigner is required for Solana transfers but was not provided.',\n });\n }\n\n if (!isSolAddress(quote.fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid Solana address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-SVM swap response from Markr, but only SVM swaps are supported for this source chain.',\n });\n }\n\n const swapTransactionBase64 = await refreshSolanaSwapTransactionBlockhash(\n swap.swapTransaction,\n getSolanaRpcForChain({ chain: quote.sourceChain }),\n );\n\n const step: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await solanaSigner.signAndSend(\n {\n account: quote.fromAddress,\n serializedTx: swapTransactionBase64,\n },\n step,\n );\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: SOLANA_REQUIRED_CONFIRMATIONS,\n startedAtMs,\n txHash,\n },\n };\n}\n\n/**\n * Determines whether one-click swap eligibility criteria is met.\n *\n * Only same-chain EVM swaps are currently eligible.\n */\nfunction isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch,\n sourceChainId,\n targetChainId,\n}: {\n approvalRequest?: EvmTransactionRequest;\n requiresApprovalSignature: boolean;\n signBatch?: EvmSigner['signBatch'];\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n}): boolean {\n return (\n isEvmNamespace(sourceChainId) &&\n isEvmNamespace(targetChainId) &&\n sourceChainId === targetChainId &&\n requiresApprovalSignature &&\n approvalRequest !== undefined &&\n typeof signBatch === 'function'\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ServiceType as e,TransferSignatureReason as t}from"../../../constants.js";import{ErrorCode as n,ErrorReason as r,SdkError as i}from"../../../errors.js";import{caip2ToEip155ChainId as a,caip2ToEip155HexChainId as o}from"../../../utils/caip.js";import{isEvmNamespace as s,isSolanaNamespace as c}from"../../../_utils/chain.js";import{applyFeeUnitsBpsMargin as l,getEvmClientForChain as u,makeFailedTransferFromQuote as
|
|
1
|
+
import{ServiceType as e,TransferSignatureReason as t}from"../../../constants.js";import{ErrorCode as n,ErrorReason as r,SdkError as i}from"../../../errors.js";import{caip2ToEip155ChainId as a,caip2ToEip155HexChainId as o}from"../../../utils/caip.js";import{isEvmNamespace as s,isSolanaNamespace as c}from"../../../_utils/chain.js";import{applyFeeUnitsBpsMargin as l,getEvmClientForChain as u,getSolanaRpcForChain as d,makeFailedTransferFromQuote as f}from"../../_utils.js";import{markrGetSpenderAddress as p,markrSwap as m}from"../_api.js";import{isSolAddress as h}from"../../../utils/sol-address.js";import{assetToAddressString as g,calculateMarkrMinimumAmountOut as _,isTokenAddressNative as v}from"../_utils.js";import{isEvmSwapResponse as y,isSolanaSwapResponse as b}from"../_type-guards.js";import{refreshSolanaSwapTransactionBlockhash as x}from"../_solana-utils.js";import{_estimateGasFromSwapResponse as S}from"./estimate-native-fee.js";import{SOLANA_REQUIRED_CONFIRMATIONS as C}from"../constants.js";import{encodeFunctionData as w,erc20Abi as T,isAddress as E}from"viem";function D({apiOptions:t,appId:a,environment:o,evmSigner:l,solanaSigner:u}){return async({quote:d,gasSettings:f,fallbackToDefaultOnBatchFailure:p,onStepChange:m})=>{if(d.serviceType!==e.MARKR)throw new i(r.INCORRECT_PROVIDER_PROVIDED,n.INVALID_PARAMS);let h=Math.floor(Date.now()/1e3);if(d.expiresAt<=h)throw new i(r.QUOTE_EXPIRED,n.INVALID_PARAMS);let g=d.sourceChain.chainId;if(c(g))return k({apiOptions:t,appId:a,environment:o,solanaSigner:u,quote:d,onStepChange:m});if(s(g))return O({apiOptions:t,appId:a,environment:o,evmSigner:l,quote:d,gasSettings:f,fallbackToDefaultOnBatchFailure:p,onStepChange:m});throw new i(r.CHAIN_NOT_SUPPORTED,n.INVALID_PARAMS,{details:`Unsupported source chain namespace: ${g}`})}}async function O({apiOptions:s,appId:c,environment:d,evmSigner:h,quote:b,gasSettings:x,fallbackToDefaultOnBatchFailure:C,onStepChange:D}){let O=b.fromAddress;if(!E(O))throw new i(r.INVALID_PARAMS,n.INVALID_PARAMS,{details:`fromAddress is not a valid EVM address.`});let k=_({amountOut:b.amountOut,assetOut:b.assetOut,slippageBps:b.slippageBps}),j=u({chain:b.sourceChain}),M=g(b.assetIn,b.sourceChain.chainId),N=g(b.assetOut,b.targetChain.chainId);if(!E(M))throw new i(r.INVALID_PARAMS,n.INVALID_PARAMS,{details:`assetIn address is not a valid EVM address.`});let P=M,F=v(P),I=b.sourceChain.chainId.toLowerCase()!==b.targetChain.chainId.toLowerCase(),{address:L}=await p(s,{chainId:a(b.sourceChain.chainId),crossChainSwap:I,quoteId:b.id});if(!F&&!L)throw new i(r.CHAIN_NOT_SUPPORTED,n.INVALID_PARAMS,{details:`Missing Markr spender address for source chain ${b.sourceChain.chainId}.`});let R=!1,z;if(!F&&L&&await j.readContract({address:P,abi:T,functionName:`allowance`,args:[O,L]})<b.amountIn){R=!0;let e=w({abi:T,functionName:`approve`,args:[L,b.amountIn]}),t;try{t=l(await j.estimateGas({account:O,to:P,data:e,value:0n}),x?.estimateGasMarginBps)}catch(e){throw new i(`Error during gas estimation`,n.VIEM_ERROR,{cause:e,details:`Failed to estimate gas for ERC20 approval transaction.`})}z={chainId:o(b.sourceChain.chainId),data:e,from:O,gas:t,to:P,value:0n,...x?.maxFeePerGas===void 0?null:{maxFeePerGas:x.maxFeePerGas,maxPriorityFeePerGas:x.maxPriorityFeePerGas}}}let B=A({approvalRequest:z,requiresApprovalSignature:R,signBatch:h.signBatch,sourceChainId:b.sourceChain.chainId,targetChainId:b.targetChain.chainId}),V=await m(s,{amountIn:b.amountIn.toString(),appId:c,minAmountOut:k.toString(),tokenIn:M,tokenOut:N,uuid:b.id});if(!y(V))throw new i(r.CHAIN_NOT_SUPPORTED,n.INVALID_PARAMS,{details:`Received non-EVM swap response from Markr, but only EVM swaps are supported for this source chain.`});let H=(t,n=Date.now())=>{let r=b.sourceChain.chainId===b.targetChain.chainId;return{amountIn:b.amountIn,amountOut:b.amountOut,environment:d,fees:b.fees,fromAddress:b.fromAddress,id:b.id,partnerFeeBps:b.partnerFeeBps,sourceAsset:b.assetIn,sourceChain:b.sourceChain,status:`source-pending`,targetAsset:b.assetOut,targetChain:b.targetChain,toAddress:b.toAddress,type:e.MARKR,source:{confirmationCount:0,requiredConfirmationCount:r?1:2,startedAtMs:n,txHash:t}}};if(B&&z){let e={currentSignature:1,currentSignatureReason:t.TokensTransfer,quote:b,requiredSignatures:1},r=h.signBatch;if(!r)throw new i(`One-click batch signer is not available.`,n.SIGNING_FAILED);let a={chainId:o(b.sourceChain.chainId),data:V.data,from:O,gas:void 0,to:V.to,value:V.value,...x?.maxFeePerGas===void 0?null:{maxFeePerGas:x.maxFeePerGas,maxPriorityFeePerGas:x.maxPriorityFeePerGas}};D?.(e);try{let t=(await r([z,a],async e=>j.sendRawTransaction({serializedTransaction:e}),e)).at(-1);if(!t)throw new i(`One-click batch signing returned no transaction hashes.`,n.SIGNING_FAILED);return H(t)}catch(e){if(!C)throw e}}if(R&&z){let e={currentSignature:1,currentSignatureReason:t.AllowanceApproval,quote:b,requiredSignatures:2};D?.(e);let r=await h.sign(z,async e=>j.sendRawTransaction({serializedTransaction:e}),e);if((await j.waitForTransactionReceipt({hash:r})).status===`reverted`)return f(b,{environment:d,errorCode:n.TRANSACTION_REVERTED,errorReason:`ERC20 approval transaction was reverted`})}let U=await S({crossChain:I,fromAddress:O,feeUnitsMarginBps:x?.estimateGasMarginBps,sourceClient:j,swap:V}),W={chainId:o(b.sourceChain.chainId),data:V.data,from:O,gas:U,to:V.to,value:V.value,...x?.maxFeePerGas===void 0?null:{maxFeePerGas:x.maxFeePerGas,maxPriorityFeePerGas:x.maxPriorityFeePerGas}},G={currentSignature:R?2:1,currentSignatureReason:t.TokensTransfer,quote:b,requiredSignatures:R?2:1};D?.(G);let K=Date.now();return H(await h.sign(W,async e=>j.sendRawTransaction({serializedTransaction:e}),G),K)}async function k({apiOptions:a,appId:o,environment:s,solanaSigner:c,quote:l,onStepChange:u}){if(!c)throw new i(r.INVALID_PARAMS,n.INVALID_PARAMS,{details:`solanaSigner is required for Solana transfers but was not provided.`});if(!h(l.fromAddress))throw new i(r.INVALID_PARAMS,n.INVALID_PARAMS,{details:`fromAddress is not a valid Solana address.`});let f=_({amountOut:l.amountOut,assetOut:l.assetOut,slippageBps:l.slippageBps}),p=g(l.assetIn,l.sourceChain.chainId),v=g(l.assetOut,l.targetChain.chainId),y=await m(a,{amountIn:l.amountIn.toString(),appId:o,minAmountOut:f.toString(),tokenIn:p,tokenOut:v,userPublicKey:l.fromAddress,uuid:l.id});if(!b(y))throw new i(r.CHAIN_NOT_SUPPORTED,n.INVALID_PARAMS,{details:`Received non-SVM swap response from Markr, but only SVM swaps are supported for this source chain.`});let S=await x(y.swapTransaction,d({chain:l.sourceChain})),w={currentSignature:1,currentSignatureReason:t.TokensTransfer,quote:l,requiredSignatures:1};u?.(w);let T=Date.now(),E=await c.signAndSend({account:l.fromAddress,serializedTx:S},w);return{amountIn:l.amountIn,amountOut:l.amountOut,environment:s,fees:l.fees,fromAddress:l.fromAddress,id:l.id,partnerFeeBps:l.partnerFeeBps,sourceAsset:l.assetIn,sourceChain:l.sourceChain,status:`source-pending`,targetAsset:l.assetOut,targetChain:l.targetChain,toAddress:l.toAddress,type:e.MARKR,source:{confirmationCount:0,requiredConfirmationCount:C,startedAtMs:T,txHash:E}}}function A({approvalRequest:e,requiresApprovalSignature:t,signBatch:n,sourceChainId:r,targetChainId:i}){return s(r)&&s(i)&&r===i&&t&&e!==void 0&&typeof n==`function`}export{D as transferAssetFactory};
|
|
2
2
|
//# sourceMappingURL=transfer-asset.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer-asset.js","names":[],"sources":["../../../../src/transfer-service/markr/_handlers/transfer-asset.ts"],"sourcesContent":["import { encodeFunctionData, erc20Abi, isAddress } from 'viem';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { ServiceType, TransferSignatureReason } from '../../../constants';\nimport type { Environment } from '../../../constants';\nimport { ErrorCode, ErrorReason, SdkError } from '../../../errors';\nimport type { TransferService } from '../../../types/service';\nimport type { EvmSigner, EvmTransactionRequest, SolanaSigner } from '../../../types/signer';\nimport type { Transfer, TransferStepDetails } from '../../../types/transfer';\nimport { caip2ToEip155ChainId, caip2ToEip155HexChainId } from '../../../utils/caip';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport { applyFeeUnitsBpsMargin, getEvmClientForChain, makeFailedTransferFromQuote } from '../../_utils';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport { assetToAddressString, calculateMarkrMinimumAmountOut, isTokenAddressNative } from '../_utils';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { SOLANA_REQUIRED_CONFIRMATIONS } from '../constants';\nimport { _estimateGasFromSwapResponse } from './estimate-native-fee';\nimport type { Caip2ChainId } from '../../../types/caip';\n\nexport interface TransferAssetFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n solanaSigner?: SolanaSigner;\n}\n\nexport function transferAssetFactory({\n apiOptions,\n appId,\n environment,\n evmSigner,\n solanaSigner,\n}: TransferAssetFactoryConfig): TransferService['transferAsset'] {\n return async ({ quote, gasSettings, fallbackToDefaultOnBatchFailure, onStepChange }) => {\n if (quote.serviceType !== ServiceType.MARKR) {\n throw new SdkError(ErrorReason.INCORRECT_PROVIDER_PROVIDED, ErrorCode.INVALID_PARAMS);\n }\n\n const now = Math.floor(Date.now() / 1_000);\n\n if (quote.expiresAt <= now) {\n throw new SdkError(ErrorReason.QUOTE_EXPIRED, ErrorCode.INVALID_PARAMS);\n }\n\n const sourceChainId = quote.sourceChain.chainId;\n\n if (isSolanaNamespace(sourceChainId)) {\n return _executeSvmTransfer({ apiOptions, appId, environment, solanaSigner, quote, onStepChange });\n }\n\n if (isEvmNamespace(sourceChainId)) {\n return _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n });\n }\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Unsupported source chain namespace: ${sourceChainId}`,\n });\n };\n}\n\n// ---------------------------------------------------------------------------\n// EVM transfer path\n// ---------------------------------------------------------------------------\n\ninterface EvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n gasSettings?: Parameters<TransferService['transferAsset']>[0]['gasSettings'];\n fallbackToDefaultOnBatchFailure?: Parameters<TransferService['transferAsset']>[0]['fallbackToDefaultOnBatchFailure'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n}: EvmTransferParams): Promise<Transfer> {\n const fromAddress = quote.fromAddress;\n\n if (!isAddress(fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid EVM address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n if (!isAddress(tokenInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'assetIn address is not a valid EVM address.',\n });\n }\n\n const tokenInAddress = tokenInAddressString;\n const isTokenInNative = isTokenAddressNative(tokenInAddress);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n if (!isTokenInNative && !spenderAddress) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Missing Markr spender address for source chain ${quote.sourceChain.chainId}.`,\n });\n }\n\n let requiresApprovalSignature = false;\n let approvalRequest: EvmTransactionRequest | undefined;\n\n if (!isTokenInNative && spenderAddress) {\n const allowance = await sourceClient.readContract({\n address: tokenInAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [fromAddress, spenderAddress],\n });\n\n if (allowance < quote.amountIn) {\n requiresApprovalSignature = true;\n\n const approvalData = encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n });\n\n let approvalGasWithMargin: bigint | undefined;\n\n try {\n const approvalGasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: tokenInAddress,\n data: approvalData,\n value: 0n,\n });\n\n approvalGasWithMargin = applyFeeUnitsBpsMargin(approvalGasEstimate, gasSettings?.estimateGasMarginBps);\n } catch (err) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n\n approvalRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: approvalData,\n from: fromAddress,\n gas: approvalGasWithMargin,\n to: tokenInAddress,\n value: 0n,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n }\n }\n\n const maybeOneClickBatch = isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch: evmSigner.signBatch,\n sourceChainId: quote.sourceChain.chainId,\n targetChainId: quote.targetChain.chainId,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-EVM swap response from Markr, but only EVM swaps are supported for this source chain.',\n });\n }\n\n const makePendingTransfer = (txHash: `0x${string}`, startedAtMs: number = Date.now()): Transfer => {\n const isSameChainTransfer = quote.sourceChain.chainId === quote.targetChain.chainId;\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: isSameChainTransfer ? 1 : 2,\n startedAtMs,\n txHash,\n },\n };\n };\n\n if (maybeOneClickBatch && approvalRequest) {\n const batchStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n const signBatch = evmSigner.signBatch;\n\n if (!signBatch) {\n throw new SdkError('One-click batch signer is not available.', ErrorCode.SIGNING_FAILED);\n }\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n // Gas is purposely left undefined here.\n // We can't estimate the gas because we aren't executing the\n // approval tx. It's up to the wallet to correctly handle gas estimation.\n gas: undefined,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n onStepChange?.(batchStep);\n\n try {\n const txHashes = await signBatch(\n [approvalRequest, swapRequest],\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n batchStep,\n );\n\n const swapTxHash = txHashes.at(-1);\n\n if (!swapTxHash) {\n throw new SdkError('One-click batch signing returned no transaction hashes.', ErrorCode.SIGNING_FAILED);\n }\n\n return makePendingTransfer(swapTxHash);\n } catch (error) {\n if (!fallbackToDefaultOnBatchFailure) {\n throw error;\n }\n }\n }\n\n if (requiresApprovalSignature && approvalRequest) {\n const approvalStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.AllowanceApproval,\n quote,\n requiredSignatures: 2,\n };\n\n onStepChange?.(approvalStep);\n\n const approvalTxHash = await evmSigner.sign(\n approvalRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n approvalStep,\n );\n\n const approvalReceipt = await sourceClient.waitForTransactionReceipt({ hash: approvalTxHash });\n\n if (approvalReceipt.status === 'reverted') {\n return makeFailedTransferFromQuote(quote, {\n environment,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'ERC20 approval transaction was reverted',\n });\n }\n }\n\n const swapGasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress,\n feeUnitsMarginBps: gasSettings?.estimateGasMarginBps,\n sourceClient,\n swap,\n });\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n gas: swapGasWithMargin,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n const step: TransferStepDetails = {\n currentSignature: requiresApprovalSignature ? 2 : 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: requiresApprovalSignature ? 2 : 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await evmSigner.sign(\n swapRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n step,\n );\n\n return makePendingTransfer(txHash, startedAtMs);\n}\n\n// ---------------------------------------------------------------------------\n// SVM (Solana) transfer path\n// ---------------------------------------------------------------------------\n\ninterface SvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n solanaSigner?: SolanaSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeSvmTransfer({\n apiOptions,\n appId,\n environment,\n solanaSigner,\n quote,\n onStepChange,\n}: SvmTransferParams): Promise<Transfer> {\n if (!solanaSigner) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'solanaSigner is required for Solana transfers but was not provided.',\n });\n }\n\n if (!isSolAddress(quote.fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid Solana address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-SVM swap response from Markr, but only SVM swaps are supported for this source chain.',\n });\n }\n\n const step: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await solanaSigner.signAndSend(\n {\n account: quote.fromAddress,\n serializedTx: swap.swapTransaction,\n },\n step,\n );\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: SOLANA_REQUIRED_CONFIRMATIONS,\n startedAtMs,\n txHash,\n },\n };\n}\n\n/**\n * Determines whether one-click swap eligibility criteria is met.\n *\n * Only same-chain EVM swaps are currently eligible.\n */\nfunction isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch,\n sourceChainId,\n targetChainId,\n}: {\n approvalRequest?: EvmTransactionRequest;\n requiresApprovalSignature: boolean;\n signBatch?: EvmSigner['signBatch'];\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n}): boolean {\n return (\n isEvmNamespace(sourceChainId) &&\n isEvmNamespace(targetChainId) &&\n sourceChainId === targetChainId &&\n requiresApprovalSignature &&\n approvalRequest !== undefined &&\n typeof signBatch === 'function'\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transfer-asset.js","names":[],"sources":["../../../../src/transfer-service/markr/_handlers/transfer-asset.ts"],"sourcesContent":["import { encodeFunctionData, erc20Abi, isAddress } from 'viem';\nimport { isEvmNamespace, isSolanaNamespace } from '../../../_utils/chain';\nimport { ServiceType, TransferSignatureReason } from '../../../constants';\nimport type { Environment } from '../../../constants';\nimport { ErrorCode, ErrorReason, SdkError } from '../../../errors';\nimport type { TransferService } from '../../../types/service';\nimport type { EvmSigner, EvmTransactionRequest, SolanaSigner } from '../../../types/signer';\nimport type { Transfer, TransferStepDetails } from '../../../types/transfer';\nimport { caip2ToEip155ChainId, caip2ToEip155HexChainId } from '../../../utils/caip';\nimport { isSolAddress } from '../../../utils/sol-address';\nimport {\n applyFeeUnitsBpsMargin,\n getEvmClientForChain,\n getSolanaRpcForChain,\n makeFailedTransferFromQuote,\n} from '../../_utils';\nimport { markrGetSpenderAddress, markrSwap, type ApiOptions } from '../_api';\nimport { assetToAddressString, calculateMarkrMinimumAmountOut, isTokenAddressNative } from '../_utils';\nimport { isEvmSwapResponse, isSolanaSwapResponse } from '../_type-guards';\nimport { SOLANA_REQUIRED_CONFIRMATIONS } from '../constants';\nimport { _estimateGasFromSwapResponse } from './estimate-native-fee';\nimport type { Caip2ChainId } from '../../../types/caip';\nimport { refreshSolanaSwapTransactionBlockhash } from '../_solana-utils';\n\nexport interface TransferAssetFactoryConfig {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n solanaSigner?: SolanaSigner;\n}\n\nexport function transferAssetFactory({\n apiOptions,\n appId,\n environment,\n evmSigner,\n solanaSigner,\n}: TransferAssetFactoryConfig): TransferService['transferAsset'] {\n return async ({ quote, gasSettings, fallbackToDefaultOnBatchFailure, onStepChange }) => {\n if (quote.serviceType !== ServiceType.MARKR) {\n throw new SdkError(ErrorReason.INCORRECT_PROVIDER_PROVIDED, ErrorCode.INVALID_PARAMS);\n }\n\n const now = Math.floor(Date.now() / 1_000);\n\n if (quote.expiresAt <= now) {\n throw new SdkError(ErrorReason.QUOTE_EXPIRED, ErrorCode.INVALID_PARAMS);\n }\n\n const sourceChainId = quote.sourceChain.chainId;\n\n if (isSolanaNamespace(sourceChainId)) {\n return _executeSvmTransfer({ apiOptions, appId, environment, solanaSigner, quote, onStepChange });\n }\n\n if (isEvmNamespace(sourceChainId)) {\n return _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n });\n }\n\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Unsupported source chain namespace: ${sourceChainId}`,\n });\n };\n}\n\n// ---------------------------------------------------------------------------\n// EVM transfer path\n// ---------------------------------------------------------------------------\n\ninterface EvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n evmSigner: EvmSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n gasSettings?: Parameters<TransferService['transferAsset']>[0]['gasSettings'];\n fallbackToDefaultOnBatchFailure?: Parameters<TransferService['transferAsset']>[0]['fallbackToDefaultOnBatchFailure'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeEvmTransfer({\n apiOptions,\n appId,\n environment,\n evmSigner,\n quote,\n gasSettings,\n fallbackToDefaultOnBatchFailure,\n onStepChange,\n}: EvmTransferParams): Promise<Transfer> {\n const fromAddress = quote.fromAddress;\n\n if (!isAddress(fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid EVM address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const sourceClient = getEvmClientForChain({ chain: quote.sourceChain });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n if (!isAddress(tokenInAddressString)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'assetIn address is not a valid EVM address.',\n });\n }\n\n const tokenInAddress = tokenInAddressString;\n const isTokenInNative = isTokenAddressNative(tokenInAddress);\n const isCrossChainSwap = quote.sourceChain.chainId.toLowerCase() !== quote.targetChain.chainId.toLowerCase();\n\n const { address: spenderAddress } = await markrGetSpenderAddress(apiOptions, {\n chainId: caip2ToEip155ChainId(quote.sourceChain.chainId),\n crossChainSwap: isCrossChainSwap,\n quoteId: quote.id,\n });\n\n if (!isTokenInNative && !spenderAddress) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: `Missing Markr spender address for source chain ${quote.sourceChain.chainId}.`,\n });\n }\n\n let requiresApprovalSignature = false;\n let approvalRequest: EvmTransactionRequest | undefined;\n\n if (!isTokenInNative && spenderAddress) {\n const allowance = await sourceClient.readContract({\n address: tokenInAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [fromAddress, spenderAddress],\n });\n\n if (allowance < quote.amountIn) {\n requiresApprovalSignature = true;\n\n const approvalData = encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spenderAddress, quote.amountIn],\n });\n\n let approvalGasWithMargin: bigint | undefined;\n\n try {\n const approvalGasEstimate = await sourceClient.estimateGas({\n account: fromAddress,\n to: tokenInAddress,\n data: approvalData,\n value: 0n,\n });\n\n approvalGasWithMargin = applyFeeUnitsBpsMargin(approvalGasEstimate, gasSettings?.estimateGasMarginBps);\n } catch (err) {\n throw new SdkError('Error during gas estimation', ErrorCode.VIEM_ERROR, {\n cause: err,\n details: 'Failed to estimate gas for ERC20 approval transaction.',\n });\n }\n\n approvalRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: approvalData,\n from: fromAddress,\n gas: approvalGasWithMargin,\n to: tokenInAddress,\n value: 0n,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n }\n }\n\n const maybeOneClickBatch = isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch: evmSigner.signBatch,\n sourceChainId: quote.sourceChain.chainId,\n targetChainId: quote.targetChain.chainId,\n });\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n uuid: quote.id,\n });\n\n if (!isEvmSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-EVM swap response from Markr, but only EVM swaps are supported for this source chain.',\n });\n }\n\n const makePendingTransfer = (txHash: `0x${string}`, startedAtMs: number = Date.now()): Transfer => {\n const isSameChainTransfer = quote.sourceChain.chainId === quote.targetChain.chainId;\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: isSameChainTransfer ? 1 : 2,\n startedAtMs,\n txHash,\n },\n };\n };\n\n if (maybeOneClickBatch && approvalRequest) {\n const batchStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n const signBatch = evmSigner.signBatch;\n\n if (!signBatch) {\n throw new SdkError('One-click batch signer is not available.', ErrorCode.SIGNING_FAILED);\n }\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n // Gas is purposely left undefined here.\n // We can't estimate the gas because we aren't executing the\n // approval tx. It's up to the wallet to correctly handle gas estimation.\n gas: undefined,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n onStepChange?.(batchStep);\n\n try {\n const txHashes = await signBatch(\n [approvalRequest, swapRequest],\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n batchStep,\n );\n\n const swapTxHash = txHashes.at(-1);\n\n if (!swapTxHash) {\n throw new SdkError('One-click batch signing returned no transaction hashes.', ErrorCode.SIGNING_FAILED);\n }\n\n return makePendingTransfer(swapTxHash);\n } catch (error) {\n if (!fallbackToDefaultOnBatchFailure) {\n throw error;\n }\n }\n }\n\n if (requiresApprovalSignature && approvalRequest) {\n const approvalStep: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.AllowanceApproval,\n quote,\n requiredSignatures: 2,\n };\n\n onStepChange?.(approvalStep);\n\n const approvalTxHash = await evmSigner.sign(\n approvalRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n approvalStep,\n );\n\n const approvalReceipt = await sourceClient.waitForTransactionReceipt({ hash: approvalTxHash });\n\n if (approvalReceipt.status === 'reverted') {\n return makeFailedTransferFromQuote(quote, {\n environment,\n errorCode: ErrorCode.TRANSACTION_REVERTED,\n errorReason: 'ERC20 approval transaction was reverted',\n });\n }\n }\n\n const swapGasWithMargin = await _estimateGasFromSwapResponse({\n crossChain: isCrossChainSwap,\n fromAddress,\n feeUnitsMarginBps: gasSettings?.estimateGasMarginBps,\n sourceClient,\n swap,\n });\n\n const swapRequest: EvmTransactionRequest = {\n chainId: caip2ToEip155HexChainId(quote.sourceChain.chainId),\n data: swap.data,\n from: fromAddress,\n gas: swapGasWithMargin,\n to: swap.to,\n value: swap.value,\n ...(gasSettings?.maxFeePerGas !== undefined\n ? {\n maxFeePerGas: gasSettings.maxFeePerGas,\n maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas,\n }\n : null),\n };\n\n const step: TransferStepDetails = {\n currentSignature: requiresApprovalSignature ? 2 : 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: requiresApprovalSignature ? 2 : 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await evmSigner.sign(\n swapRequest,\n async (signedTxHash) => sourceClient.sendRawTransaction({ serializedTransaction: signedTxHash }),\n step,\n );\n\n return makePendingTransfer(txHash, startedAtMs);\n}\n\n// ---------------------------------------------------------------------------\n// SVM (Solana) transfer path\n// ---------------------------------------------------------------------------\n\ninterface SvmTransferParams {\n apiOptions: ApiOptions;\n appId: string;\n environment: Environment;\n solanaSigner?: SolanaSigner;\n quote: Parameters<TransferService['transferAsset']>[0]['quote'];\n onStepChange?: Parameters<TransferService['transferAsset']>[0]['onStepChange'];\n}\n\nasync function _executeSvmTransfer({\n apiOptions,\n appId,\n environment,\n solanaSigner,\n quote,\n onStepChange,\n}: SvmTransferParams): Promise<Transfer> {\n if (!solanaSigner) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'solanaSigner is required for Solana transfers but was not provided.',\n });\n }\n\n if (!isSolAddress(quote.fromAddress)) {\n throw new SdkError(ErrorReason.INVALID_PARAMS, ErrorCode.INVALID_PARAMS, {\n details: 'fromAddress is not a valid Solana address.',\n });\n }\n\n const minAmountOut = calculateMarkrMinimumAmountOut({\n amountOut: quote.amountOut,\n assetOut: quote.assetOut,\n slippageBps: quote.slippageBps,\n });\n\n const tokenInAddressString = assetToAddressString(quote.assetIn, quote.sourceChain.chainId);\n const tokenOutAddressString = assetToAddressString(quote.assetOut, quote.targetChain.chainId);\n\n const swap = await markrSwap(apiOptions, {\n amountIn: quote.amountIn.toString(),\n appId,\n minAmountOut: minAmountOut.toString(),\n tokenIn: tokenInAddressString,\n tokenOut: tokenOutAddressString,\n userPublicKey: quote.fromAddress,\n uuid: quote.id,\n });\n\n if (!isSolanaSwapResponse(swap)) {\n throw new SdkError(ErrorReason.CHAIN_NOT_SUPPORTED, ErrorCode.INVALID_PARAMS, {\n details: 'Received non-SVM swap response from Markr, but only SVM swaps are supported for this source chain.',\n });\n }\n\n const swapTransactionBase64 = await refreshSolanaSwapTransactionBlockhash(\n swap.swapTransaction,\n getSolanaRpcForChain({ chain: quote.sourceChain }),\n );\n\n const step: TransferStepDetails = {\n currentSignature: 1,\n currentSignatureReason: TransferSignatureReason.TokensTransfer,\n quote,\n requiredSignatures: 1,\n };\n\n onStepChange?.(step);\n\n const startedAtMs = Date.now();\n\n const txHash = await solanaSigner.signAndSend(\n {\n account: quote.fromAddress,\n serializedTx: swapTransactionBase64,\n },\n step,\n );\n\n return {\n amountIn: quote.amountIn,\n amountOut: quote.amountOut,\n environment,\n fees: quote.fees,\n fromAddress: quote.fromAddress,\n id: quote.id,\n partnerFeeBps: quote.partnerFeeBps,\n sourceAsset: quote.assetIn,\n sourceChain: quote.sourceChain,\n status: 'source-pending',\n targetAsset: quote.assetOut,\n targetChain: quote.targetChain,\n toAddress: quote.toAddress,\n type: ServiceType.MARKR,\n source: {\n confirmationCount: 0,\n requiredConfirmationCount: SOLANA_REQUIRED_CONFIRMATIONS,\n startedAtMs,\n txHash,\n },\n };\n}\n\n/**\n * Determines whether one-click swap eligibility criteria is met.\n *\n * Only same-chain EVM swaps are currently eligible.\n */\nfunction isOneClickBatchEligible({\n approvalRequest,\n requiresApprovalSignature,\n signBatch,\n sourceChainId,\n targetChainId,\n}: {\n approvalRequest?: EvmTransactionRequest;\n requiresApprovalSignature: boolean;\n signBatch?: EvmSigner['signBatch'];\n sourceChainId: Caip2ChainId;\n targetChainId: Caip2ChainId;\n}): boolean {\n return (\n isEvmNamespace(sourceChainId) &&\n isEvmNamespace(targetChainId) &&\n sourceChainId === targetChainId &&\n requiresApprovalSignature &&\n approvalRequest !== undefined &&\n typeof signBatch === 'function'\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../errors.cjs`);let t=require(`zod`),n=require(`@solana/kit`);const r=t.z.base64();function i(t){if(!r.safeParse(t).success)throw new e.InvalidParamsError(`Not a valid base64 string`,`Expected a base64-encoded Solana Transaction string.`);let i=Uint8Array.from(atob(t),e=>e.charCodeAt(0)),a;try{a=(0,n.getTransactionDecoder)().decode(i)}catch(t){throw new e.SdkError(`Failed to decode transaction.`,e.ErrorCode.SOLANA_ERROR,{cause:t})}return a}async function a(e,t){return await(0,n.decompileTransactionMessageFetchingLookupTables)((0,n.getCompiledTransactionMessageDecoder)().decode(e.messageBytes),t)}async function o(e,t){let r=i(e),[o,s]=await Promise.all([a(r,t),t.getLatestBlockhash().send()]);return(0,n.getBase64EncodedWireTransaction)((0,n.compileTransaction)((0,n.setTransactionMessageLifetimeUsingBlockhash)(s.value,o)))}exports.decodeSolanaTransactionBase64=i,exports.refreshSolanaSwapTransactionBlockhash=o;
|
|
2
|
+
//# sourceMappingURL=_solana-utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_solana-utils.cjs","names":["z","InvalidParamsError","SdkError","ErrorCode"],"sources":["../../../src/transfer-service/markr/_solana-utils.ts"],"sourcesContent":["import {\n compileTransaction,\n decompileTransactionMessageFetchingLookupTables,\n getBase64EncodedWireTransaction,\n getCompiledTransactionMessageDecoder,\n getTransactionDecoder,\n setTransactionMessageLifetimeUsingBlockhash,\n type Base64EncodedWireTransaction,\n type Rpc,\n type SolanaRpcApi,\n type Transaction,\n} from '@solana/kit';\nimport { z } from 'zod';\nimport { ErrorCode, InvalidParamsError, SdkError } from '../../errors';\n\nconst SolanaSwapTransactionBase64Schema = z.base64();\n\n/**\n * Decodes a base64-encoded serialized Solana transaction.\n *\n * Validates that the input is base64 before attempting to deserialize.\n * Throws SDK errors with stable codes so callers can map failures.\n */\nexport function decodeSolanaTransactionBase64(swapTransactionBase64: string): Transaction {\n const parsedBase64 = SolanaSwapTransactionBase64Schema.safeParse(swapTransactionBase64);\n\n if (!parsedBase64.success) {\n throw new InvalidParamsError('Not a valid base64 string', 'Expected a base64-encoded Solana Transaction string.');\n }\n\n const txBytes = Uint8Array.from(atob(swapTransactionBase64), (character) => character.charCodeAt(0));\n\n let tx: Transaction;\n\n try {\n tx = getTransactionDecoder().decode(txBytes);\n } catch (error) {\n throw new SdkError('Failed to decode transaction.', ErrorCode.SOLANA_ERROR, { cause: error });\n }\n\n return tx;\n}\n\n/**\n * Decompiles a transaction message, resolving address lookup tables via RPC.\n *\n * This returns a message shape that can be mutated (for example, to refresh\n * blockhash lifetime) and recompiled into a new transaction.\n */\nexport async function getDecompiledMessageFromTransaction(\n tx: Transaction,\n rpc: Rpc<SolanaRpcApi>,\n): Promise<ReturnType<typeof decompileTransactionMessageFetchingLookupTables>> {\n const compiledMessageWithLifetime = getCompiledTransactionMessageDecoder().decode(tx.messageBytes);\n\n return await decompileTransactionMessageFetchingLookupTables(compiledMessageWithLifetime, rpc);\n}\n\n/**\n * Refreshes a serialized Solana swap transaction to use the latest blockhash.\n *\n * The returned value is a base64-encoded serialized transaction suitable for\n * signing and broadcast.\n */\nexport async function refreshSolanaSwapTransactionBlockhash(\n swapTransactionBase64: string,\n rpc: Rpc<SolanaRpcApi>,\n): Promise<Base64EncodedWireTransaction> {\n const tx = decodeSolanaTransactionBase64(swapTransactionBase64);\n const [decompiledMessage, latestBlockhashResponse] = await Promise.all([\n getDecompiledMessageFromTransaction(tx, rpc),\n rpc.getLatestBlockhash().send(),\n ]);\n\n const refreshedTxMessage = setTransactionMessageLifetimeUsingBlockhash(\n latestBlockhashResponse.value,\n decompiledMessage,\n );\n\n const refreshedTx = compileTransaction(refreshedTxMessage);\n\n return getBase64EncodedWireTransaction(refreshedTx);\n}\n"],"mappings":"kIAeA,MAAM,EAAoCA,EAAAA,EAAE,QAAQ,CAQpD,SAAgB,EAA8B,EAA4C,CAGxF,GAAI,CAFiB,EAAkC,UAAU,EAAsB,CAErE,QAChB,MAAM,IAAIC,EAAAA,mBAAmB,4BAA6B,uDAAuD,CAGnH,IAAM,EAAU,WAAW,KAAK,KAAK,EAAsB,CAAG,GAAc,EAAU,WAAW,EAAE,CAAC,CAEhG,EAEJ,GAAI,CACF,GAAA,EAAA,EAAA,wBAA4B,CAAC,OAAO,EAAQ,OACrC,EAAO,CACd,MAAM,IAAIC,EAAAA,SAAS,gCAAiCC,EAAAA,UAAU,aAAc,CAAE,MAAO,EAAO,CAAC,CAG/F,OAAO,EAST,eAAsB,EACpB,EACA,EAC6E,CAG7E,OAAO,MAAA,EAAA,EAAA,kDAAA,EAAA,EAAA,uCAFmE,CAAC,OAAO,EAAG,aAAa,CAER,EAAI,CAShG,eAAsB,EACpB,EACA,EACuC,CACvC,IAAM,EAAK,EAA8B,EAAsB,CACzD,CAAC,EAAmB,GAA2B,MAAM,QAAQ,IAAI,CACrE,EAAoC,EAAI,EAAI,CAC5C,EAAI,oBAAoB,CAAC,MAAM,CAChC,CAAC,CASF,OAAA,EAAA,EAAA,kCAAA,EAAA,EAAA,qBAAA,EAAA,EAAA,6CANE,EAAwB,MACxB,EACD,CAEyD,CAEP"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ErrorCode as e,InvalidParamsError as t,SdkError as n}from"../../errors.js";import{z as r}from"zod";import{compileTransaction as i,decompileTransactionMessageFetchingLookupTables as a,getBase64EncodedWireTransaction as o,getCompiledTransactionMessageDecoder as s,getTransactionDecoder as c,setTransactionMessageLifetimeUsingBlockhash as l}from"@solana/kit";const u=r.base64();function d(r){if(!u.safeParse(r).success)throw new t(`Not a valid base64 string`,`Expected a base64-encoded Solana Transaction string.`);let i=Uint8Array.from(atob(r),e=>e.charCodeAt(0)),a;try{a=c().decode(i)}catch(t){throw new n(`Failed to decode transaction.`,e.SOLANA_ERROR,{cause:t})}return a}async function f(e,t){return await a(s().decode(e.messageBytes),t)}async function p(e,t){let n=d(e),[r,a]=await Promise.all([f(n,t),t.getLatestBlockhash().send()]);return o(i(l(a.value,r)))}export{d as decodeSolanaTransactionBase64,p as refreshSolanaSwapTransactionBlockhash};
|
|
2
|
+
//# sourceMappingURL=_solana-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_solana-utils.js","names":[],"sources":["../../../src/transfer-service/markr/_solana-utils.ts"],"sourcesContent":["import {\n compileTransaction,\n decompileTransactionMessageFetchingLookupTables,\n getBase64EncodedWireTransaction,\n getCompiledTransactionMessageDecoder,\n getTransactionDecoder,\n setTransactionMessageLifetimeUsingBlockhash,\n type Base64EncodedWireTransaction,\n type Rpc,\n type SolanaRpcApi,\n type Transaction,\n} from '@solana/kit';\nimport { z } from 'zod';\nimport { ErrorCode, InvalidParamsError, SdkError } from '../../errors';\n\nconst SolanaSwapTransactionBase64Schema = z.base64();\n\n/**\n * Decodes a base64-encoded serialized Solana transaction.\n *\n * Validates that the input is base64 before attempting to deserialize.\n * Throws SDK errors with stable codes so callers can map failures.\n */\nexport function decodeSolanaTransactionBase64(swapTransactionBase64: string): Transaction {\n const parsedBase64 = SolanaSwapTransactionBase64Schema.safeParse(swapTransactionBase64);\n\n if (!parsedBase64.success) {\n throw new InvalidParamsError('Not a valid base64 string', 'Expected a base64-encoded Solana Transaction string.');\n }\n\n const txBytes = Uint8Array.from(atob(swapTransactionBase64), (character) => character.charCodeAt(0));\n\n let tx: Transaction;\n\n try {\n tx = getTransactionDecoder().decode(txBytes);\n } catch (error) {\n throw new SdkError('Failed to decode transaction.', ErrorCode.SOLANA_ERROR, { cause: error });\n }\n\n return tx;\n}\n\n/**\n * Decompiles a transaction message, resolving address lookup tables via RPC.\n *\n * This returns a message shape that can be mutated (for example, to refresh\n * blockhash lifetime) and recompiled into a new transaction.\n */\nexport async function getDecompiledMessageFromTransaction(\n tx: Transaction,\n rpc: Rpc<SolanaRpcApi>,\n): Promise<ReturnType<typeof decompileTransactionMessageFetchingLookupTables>> {\n const compiledMessageWithLifetime = getCompiledTransactionMessageDecoder().decode(tx.messageBytes);\n\n return await decompileTransactionMessageFetchingLookupTables(compiledMessageWithLifetime, rpc);\n}\n\n/**\n * Refreshes a serialized Solana swap transaction to use the latest blockhash.\n *\n * The returned value is a base64-encoded serialized transaction suitable for\n * signing and broadcast.\n */\nexport async function refreshSolanaSwapTransactionBlockhash(\n swapTransactionBase64: string,\n rpc: Rpc<SolanaRpcApi>,\n): Promise<Base64EncodedWireTransaction> {\n const tx = decodeSolanaTransactionBase64(swapTransactionBase64);\n const [decompiledMessage, latestBlockhashResponse] = await Promise.all([\n getDecompiledMessageFromTransaction(tx, rpc),\n rpc.getLatestBlockhash().send(),\n ]);\n\n const refreshedTxMessage = setTransactionMessageLifetimeUsingBlockhash(\n latestBlockhashResponse.value,\n decompiledMessage,\n );\n\n const refreshedTx = compileTransaction(refreshedTxMessage);\n\n return getBase64EncodedWireTransaction(refreshedTx);\n}\n"],"mappings":"2WAeA,MAAM,EAAoC,EAAE,QAAQ,CAQpD,SAAgB,EAA8B,EAA4C,CAGxF,GAAI,CAFiB,EAAkC,UAAU,EAAsB,CAErE,QAChB,MAAM,IAAI,EAAmB,4BAA6B,uDAAuD,CAGnH,IAAM,EAAU,WAAW,KAAK,KAAK,EAAsB,CAAG,GAAc,EAAU,WAAW,EAAE,CAAC,CAEhG,EAEJ,GAAI,CACF,EAAK,GAAuB,CAAC,OAAO,EAAQ,OACrC,EAAO,CACd,MAAM,IAAI,EAAS,gCAAiC,EAAU,aAAc,CAAE,MAAO,EAAO,CAAC,CAG/F,OAAO,EAST,eAAsB,EACpB,EACA,EAC6E,CAG7E,OAAO,MAAM,EAFuB,GAAsC,CAAC,OAAO,EAAG,aAAa,CAER,EAAI,CAShG,eAAsB,EACpB,EACA,EACuC,CACvC,IAAM,EAAK,EAA8B,EAAsB,CACzD,CAAC,EAAmB,GAA2B,MAAM,QAAQ,IAAI,CACrE,EAAoC,EAAI,EAAI,CAC5C,EAAI,oBAAoB,CAAC,MAAM,CAChC,CAAC,CASF,OAAO,EAFa,EALO,EACzB,EAAwB,MACxB,EACD,CAEyD,CAEP"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avalabs/fusion-sdk",
|
|
3
3
|
"license": "Limited Ecosystem License",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/mod.cjs",
|
|
7
7
|
"module": "./dist/mod.js",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"vitest": "4.0.6",
|
|
48
48
|
"tsdown": "0.20.3",
|
|
49
49
|
"zod": "4.1.12",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
50
|
+
"@internal/tsdown-config": "0.0.1",
|
|
51
|
+
"eslint-config-custom": "0.1.0"
|
|
52
52
|
},
|
|
53
53
|
"sideEffects": false,
|
|
54
54
|
"scripts": {
|