@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
181 lines • 7.89 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMockSafeSigner = exports.signSafeQuote = exports.validateSafeDeployment = exports.formatSignedSafeQuotePayload = exports.computeSafeDomainSeparator = exports.SAFE_SA_PREFIX = void 0;
exports.getDataToPrepareSafeTransaction = getDataToPrepareSafeTransaction;
const types_kit_1 = require("@safe-global/types-kit");
const viem_1 = require("viem");
const accounts_1 = require("viem/accounts");
const ForwarderAbi_1 = require("../../../constants/abi/ForwarderAbi.js");
exports.SAFE_SA_PREFIX = "0x177eee04";
const SAFE_DOMAIN_TYPEHASH = "0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218";
const computeSafeDomainSeparator = (safeAddress, chainId) => {
return (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([
{ type: "bytes32" },
{ type: "uint256" },
{ type: "address" }
], [SAFE_DOMAIN_TYPEHASH, BigInt(chainId), safeAddress]));
};
exports.computeSafeDomainSeparator = computeSafeDomainSeparator;
const formatSignedSafeQuotePayload = (safeAccount, quoteParams, safeTxData, signatures) => {
const { quote } = quoteParams;
console.log("safeAccount:", safeAccount);
const encodedSafeTxnData = (0, viem_1.encodeAbiParameters)([
{ name: "safeAccount", type: "address" },
{
name: "safeTxnData",
type: "tuple",
components: [
{ name: "ogDomainSeparator", type: "bytes32" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "data", type: "bytes" },
{ name: "operation", type: "uint8" },
{ name: "safeTxGas", type: "uint256" },
{ name: "baseGas", type: "uint256" },
{ name: "gasPrice", type: "uint256" },
{ name: "gasToken", type: "address" },
{ name: "refundReceiver", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "signatures", type: "bytes" }
]
}
], [
safeAccount,
{
ogDomainSeparator: safeTxData.ogDomainSeparator,
to: safeTxData.to,
value: safeTxData.value,
data: safeTxData.data,
operation: safeTxData.operation,
safeTxGas: safeTxData.safeTxGas,
baseGas: safeTxData.baseGas,
gasPrice: safeTxData.gasPrice,
gasToken: safeTxData.gasToken,
refundReceiver: safeTxData.refundReceiver,
nonce: safeTxData.nonce,
signatures
}
]);
return {
...quote,
signature: (0, viem_1.concatHex)([exports.SAFE_SA_PREFIX, encodedSafeTxnData])
};
};
exports.formatSignedSafeQuotePayload = formatSignedSafeQuotePayload;
const getInvolvedChainIds = (quote) => {
const { userOps, paymentInfo } = quote;
const isSponsored = paymentInfo.sponsored === true;
const relevantUserOps = isSponsored ? userOps.slice(1) : userOps;
const chainIds = relevantUserOps.map((op) => Number(op.chainId));
return [...new Set(chainIds)];
};
const validateSafeDeployment = async (client, quote, safeAddress) => {
const chainIds = getInvolvedChainIds(quote);
const deploymentChecks = await Promise.all(chainIds.map(async (chainId) => {
const deployment = client.account.deploymentOn(chainId, false);
if (!deployment) {
return { chainId, deployed: false, reason: "no deployment config" };
}
const publicClient = (0, viem_1.createPublicClient)({
chain: deployment.client.chain,
transport: (0, viem_1.http)()
});
const code = await publicClient.getCode({ address: safeAddress });
const isDeployed = code !== undefined && code !== "0x";
return { chainId, deployed: isDeployed };
}));
const undeployedChains = deploymentChecks.filter((check) => !check.deployed);
if (undeployedChains.length > 0) {
const chainList = undeployedChains.map((c) => c.chainId).join(", ");
throw new Error(`Safe at ${safeAddress} is not deployed on chains: ${chainList}. Safe must be deployed on all chains involved in the supertransaction.`);
}
};
exports.validateSafeDeployment = validateSafeDeployment;
const signSafeQuote = async (client, parameters) => {
const { fusionQuote, signedSafeTxn } = parameters;
await (0, exports.validateSafeDeployment)(client, fusionQuote.quote, parameters.safeAccount);
const txnData = signedSafeTxn.data;
const signatures = signedSafeTxn.encodedSignatures();
const ogDomainSeparator = (0, exports.computeSafeDomainSeparator)(parameters.safeAccount, fusionQuote.trigger.chainId);
const safeTxData = {
ogDomainSeparator,
to: txnData.to,
value: BigInt(txnData.value),
data: txnData.data,
operation: txnData.operation,
safeTxGas: BigInt(txnData.safeTxGas),
baseGas: BigInt(txnData.baseGas),
gasPrice: BigInt(txnData.gasPrice),
gasToken: txnData.gasToken,
refundReceiver: txnData.refundReceiver,
nonce: BigInt(txnData.nonce),
signatures: signatures
};
return (0, exports.formatSignedSafeQuotePayload)(parameters.safeAccount, parameters.fusionQuote, safeTxData, signatures);
};
exports.signSafeQuote = signSafeQuote;
function getDataToPrepareSafeTransaction(client, quoteParams, companionAccount) {
const { quote, trigger } = quoteParams;
if (trigger.call) {
throw new Error("Custom triggers are not supported for Safe fusion transactions");
}
if (!trigger.amount) {
throw new Error("Amount is required to sign a Safe quote");
}
const account = companionAccount ?? client.account;
const spender = account.addressOn(trigger.chainId, true);
const recipient = trigger.recipientAddress || spender;
const { version } = account.deploymentOn(trigger.chainId, true);
const ethForwarderAddress = version.ethForwarderAddress;
const amount = trigger.approvalAmount ?? trigger.amount;
let to;
let value;
let calldata;
if (trigger.tokenAddress === viem_1.zeroAddress) {
const forwardCalldata = (0, viem_1.encodeFunctionData)({
abi: ForwarderAbi_1.ForwarderAbi,
functionName: "forward",
args: [recipient]
});
to = ethForwarderAddress;
value = amount;
calldata = forwardCalldata;
}
else {
const approveCalldata = (0, viem_1.encodeFunctionData)({
abi: viem_1.erc20Abi,
functionName: "approve",
args: [spender, amount]
});
to = trigger.tokenAddress;
value = 0n;
calldata = approveCalldata;
}
const dataWithHash = (0, viem_1.concatHex)([calldata, quote.hash]);
return {
to,
value: value.toString(),
data: dataWithHash,
operation: types_kit_1.OperationType.Call
};
}
const getMockSafeSigner = (safeAddress) => {
return (0, accounts_1.toAccount)({
address: safeAddress,
async signMessage(_) {
throw new Error("signMessage is not supported for Safe-owned signer. " +
"Use protocol-kit to sign Safe transactions instead.");
},
async signTransaction(_) {
throw new Error("signTransaction is not supported for Safe-owned signer. " +
"Use protocol-kit to sign Safe transactions instead.");
},
async signTypedData(_) {
throw new Error("signTypedData is not supported for Safe-owned signer. " +
"Use protocol-kit to sign Safe transactions instead.");
}
});
};
exports.getMockSafeSigner = getMockSafeSigner;
exports.default = exports.signSafeQuote;
//# sourceMappingURL=signSafeQuote.js.map