@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
223 lines • 7.93 kB
JavaScript
import { concat, decodeAbiParameters, encodeAbiParameters, encodeFunctionData, encodePacked, keccak256, toHex, zeroAddress, } from 'viem';
/**
* Sentinel value: resolves to the contract's token balance (ERC20) or
* native balance (ETH) at execution time. Matches QuotedCalls.CONTRACT_BALANCE.
*/
export const CONTRACT_BALANCE = 2n ** 255n;
// ============ SignedQuote ABI tuple ============
// Matches: abi.decode(input, (address, SignedQuote, bytes, bytes32))
// SignedQuote = (bytes context, bytes data, uint48 issuedAt, uint48 expiry, bytes32 salt, address submitter)
const signedQuoteTuple = {
type: 'tuple',
components: [
{ name: 'context', type: 'bytes' },
{ name: 'data', type: 'bytes' },
{ name: 'issuedAt', type: 'uint48' },
{ name: 'expiry', type: 'uint48' },
{ name: 'salt', type: 'bytes32' },
{ name: 'submitter', type: 'address' },
],
};
// ============ Permit2 PermitSingle ABI tuple ============
// Matches: abi.decode(input, (IAllowanceTransfer.PermitSingle, bytes))
const permitDetailsTuple = {
type: 'tuple',
components: [
{ name: 'token', type: 'address' },
{ name: 'amount', type: 'uint160' },
{ name: 'expiration', type: 'uint48' },
{ name: 'nonce', type: 'uint48' },
],
};
const permitSingleTuple = {
type: 'tuple',
components: [
{ name: 'details', ...permitDetailsTuple },
{ name: 'spender', type: 'address' },
{ name: 'sigDeadline', type: 'uint256' },
],
};
// ============ Salt scoping ============
/** Matches QuotedCalls._scopeSalt: keccak256(abi.encodePacked(caller, salt)) */
export function computeScopedSalt(caller, clientSalt) {
return keccak256(encodePacked(['address', 'bytes32'], [caller, clientSalt]));
}
// ============ Per-command input encoders ============
/** Encode SUBMIT_QUOTE input: abi.encode(address quoter, SignedQuote quote, bytes signature, bytes32 clientSalt) */
export function encodeSubmitQuoteInput(cmd, clientSalt) {
return encodeAbiParameters([
{ type: 'address' },
signedQuoteTuple,
{ type: 'bytes' },
{ type: 'bytes32' },
], [
cmd.quoter,
{
context: cmd.quote.context,
data: cmd.quote.data,
issuedAt: cmd.quote.issuedAt,
expiry: cmd.quote.expiry,
salt: cmd.quote.salt,
submitter: cmd.quote.submitter,
},
cmd.signature,
clientSalt,
]);
}
/** Encode PERMIT2_PERMIT input: abi.encode(IAllowanceTransfer.PermitSingle, bytes signature) */
export function encodePermit2PermitInput(permit2Data) {
const { permitSingle, signature } = permit2Data;
return encodeAbiParameters([permitSingleTuple, { type: 'bytes' }], [
{
details: {
token: permitSingle.details.token,
amount: permitSingle.details.amount,
expiration: permitSingle.details.expiration,
nonce: permitSingle.details.nonce,
},
spender: permitSingle.spender,
sigDeadline: BigInt(permitSingle.sigDeadline),
},
signature,
]);
}
/** Encode PERMIT2_TRANSFER_FROM input: abi.encode(address token, uint160 amount) */
export function encodePermit2TransferFromInput(token, amount) {
return encodeAbiParameters([{ type: 'address' }, { type: 'uint160' }], [token, amount]);
}
/** Encode TRANSFER_FROM input: abi.encode(address token, uint256 amount) */
export function encodeTransferFromInput(token, amount) {
return encodeAbiParameters([{ type: 'address' }, { type: 'uint256' }], [token, amount]);
}
/** Encode TRANSFER_REMOTE input: abi.encode(address warpRoute, uint32 destination, bytes32 recipient, uint256 amount, uint256 value, address token, uint256 approval) */
export function encodeTransferRemoteInput(params) {
return encodeAbiParameters([
{ type: 'address' },
{ type: 'uint32' },
{ type: 'bytes32' },
{ type: 'uint256' },
{ type: 'uint256' },
{ type: 'address' },
{ type: 'uint256' },
], [
params.warpRoute,
params.destination,
params.recipient,
params.amount,
params.value,
params.token,
params.approval,
]);
}
/** Encode TRANSFER_REMOTE_TO input: abi.encode(address router, uint32 destination, bytes32 recipient, uint256 amount, bytes32 targetRouter, uint256 value, address token, uint256 approval) */
export function encodeTransferRemoteToInput(params) {
return encodeAbiParameters([
{ type: 'address' },
{ type: 'uint32' },
{ type: 'bytes32' },
{ type: 'uint256' },
{ type: 'bytes32' },
{ type: 'uint256' },
{ type: 'address' },
{ type: 'uint256' },
], [
params.router,
params.destination,
params.recipient,
params.amount,
params.targetRouter,
params.value,
params.token,
params.approval,
]);
}
/** Encode SWEEP input: abi.encode(address token) */
export function encodeSweepInput(token) {
return encodeAbiParameters([{ type: 'address' }], [token]);
}
// ============ Execute calldata ============
// ============ ABI definitions ============
const quoteTuple = {
type: 'tuple',
components: [
{ name: 'token', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
};
export const quotedCallsAbi = [
{
name: 'execute',
type: 'function',
inputs: [
{ name: 'commands', type: 'bytes' },
{ name: 'inputs', type: 'bytes[]' },
],
outputs: [],
stateMutability: 'payable',
},
{
name: 'quoteExecute',
type: 'function',
inputs: [
{ name: 'commands', type: 'bytes' },
{ name: 'inputs', type: 'bytes[]' },
],
outputs: [
{
name: 'results',
type: 'tuple[][]',
components: quoteTuple.components,
},
],
stateMutability: 'nonpayable',
},
];
// ============ Encode helpers ============
function encodeCommandsBytes(commands) {
return concat(commands.map((c) => toHex(c, { size: 1 })));
}
/** Encode QuotedCalls.execute(commands, inputs) calldata */
export function encodeExecuteCalldata(commands, inputs) {
return encodeFunctionData({
abi: quotedCallsAbi,
functionName: 'execute',
args: [encodeCommandsBytes(commands), inputs],
});
}
/** Encode QuotedCalls.quoteExecute(commands, inputs) calldata */
export function encodeQuoteExecuteCalldata(commands, inputs) {
return encodeFunctionData({
abi: quotedCallsAbi,
functionName: 'quoteExecute',
args: [encodeCommandsBytes(commands), inputs],
});
}
/** Decode the return value of quoteExecute into Quote[][] */
export function decodeQuoteExecuteResult(data) {
const [results] = decodeAbiParameters([
{
type: 'tuple[][]',
components: quoteTuple.components,
},
], data);
return results.map((perCommand) => perCommand.map((q) => ({ token: q.token, amount: q.amount })));
}
/** Sum Quote[][] into totals per token address (normalized to lowercase) */
export function sumQuotesByToken(results) {
const totals = new Map();
for (const perCommand of results) {
for (const q of perCommand) {
const key = q.token.toLowerCase();
totals.set(key, (totals.get(key) ?? 0n) + q.amount);
}
}
return totals;
}
/** Extract native (address(0)) and ERC20 totals from quote results */
export function extractQuoteTotals(results) {
const all = sumQuotesByToken(results);
const nativeValue = all.get(zeroAddress) ?? 0n;
all.delete(zeroAddress);
return { nativeValue, tokenTotals: all };
}
//# sourceMappingURL=codec.js.map