-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
90133fc
commit dff3de5
Showing
30 changed files
with
1,786 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
PINATA_JWT= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
!dist/ |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
|
||
export type SendERC20Metadata = { | ||
IpfsHash: string; | ||
PinSize: number; | ||
Timestamp: string; | ||
isDuplicate: boolean; | ||
Duration: number; | ||
}; | ||
|
||
export type SendERC20LitActionString = string; | ||
|
||
export declare const sendERC20LitActionDescription: string; | ||
export declare const sendERC20LitAction: SendERC20LitActionString; | ||
export declare const sendERC20Metadata: { | ||
sendERC20LitAction: SendERC20Metadata; | ||
}; | ||
|
||
export * from "./policy"; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"sendERC20LitAction": { | ||
"IpfsHash": "Qmaso3dHHsoMuTHPiQQFtmut5J7EzHtuQ3KtkWMymhREEs", | ||
"PinSize": 6737, | ||
"Timestamp": "2024-12-20T06:49:49.328Z", | ||
"isDuplicate": true, | ||
"Duration": 0.697 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
(async () => { | ||
try { | ||
const ethersProvider = new ethers.providers.JsonRpcProvider( | ||
chainInfo.rpcUrl | ||
); | ||
const LIT_AGENT_REGISTRY_ABI = [ | ||
"function getActionPolicy(address user, address pkp, string calldata ipfsCid) external view returns (bool isPermitted, bytes memory description, bytes memory policy)" | ||
]; | ||
const LIT_AGENT_REGISTRY_ADDRESS = "0x728e8162603F35446D09961c4A285e2643f4FB91"; | ||
if (!LitAuth.authSigAddress) { | ||
throw new Error("Missing required parameter: LitAuth.authSigAddress"); | ||
} | ||
if (!LitAuth.actionIpfsIds[0]) { | ||
throw new Error("Missing required parameter: LitAuth.actionIpfsIds[0]"); | ||
} | ||
if (!pkp.ethAddress) { | ||
throw new Error("Missing required parameter: pkp.ethAddress"); | ||
} | ||
const registryContract = new ethers.Contract( | ||
LIT_AGENT_REGISTRY_ADDRESS, | ||
LIT_AGENT_REGISTRY_ABI, | ||
ethersProvider | ||
); | ||
const [isPermitted, , policy] = await registryContract.getActionPolicy( | ||
LitAuth.authSigAddress, | ||
pkp.ethAddress, | ||
LitAuth.actionIpfsIds[0] | ||
); | ||
if (!isPermitted) { | ||
throw new Error("Action not permitted for this PKP"); | ||
} | ||
const policyStruct = ["tuple(uint256 maxAmount, address[] allowedTokens, address[] allowedRecipients)"]; | ||
let decodedPolicy; | ||
try { | ||
decodedPolicy = ethers.utils.defaultAbiCoder.decode(policyStruct, policy)[0]; | ||
if (!decodedPolicy.maxAmount || !decodedPolicy.allowedTokens || !decodedPolicy.allowedRecipients) { | ||
throw new Error("Invalid policy format: missing required fields"); | ||
} | ||
decodedPolicy.allowedTokens = decodedPolicy.allowedTokens.map( | ||
(token) => ethers.utils.getAddress(token) | ||
); | ||
decodedPolicy.allowedRecipients = decodedPolicy.allowedRecipients.map( | ||
(recipient) => ethers.utils.getAddress(recipient) | ||
); | ||
} catch (error) { | ||
throw new Error( | ||
`Failed to decode policy: ${error instanceof Error ? error.message : String(error)}` | ||
); | ||
} | ||
const normalizedTokenAddress = ethers.utils.getAddress(params.tokenIn); | ||
const normalizedRecipientAddress = ethers.utils.getAddress(params.recipientAddress); | ||
if (!decodedPolicy.allowedTokens.includes(normalizedTokenAddress)) { | ||
throw new Error(`Token not allowed: ${normalizedTokenAddress}`); | ||
} | ||
if (!decodedPolicy.allowedRecipients.includes(normalizedRecipientAddress)) { | ||
throw new Error(`Recipient not allowed: ${normalizedRecipientAddress}`); | ||
} | ||
const tokenInterface = new ethers.utils.Interface([ | ||
"function decimals() view returns (uint8)", | ||
"function balanceOf(address account) view returns (uint256)", | ||
"function transfer(address to, uint256 amount) external returns (bool)" | ||
]); | ||
const tokenContract = new ethers.Contract( | ||
params.tokenIn, | ||
tokenInterface, | ||
ethersProvider | ||
); | ||
const [decimals, balance] = await Promise.all([ | ||
tokenContract.decimals(), | ||
tokenContract.balanceOf(pkp.ethAddress) | ||
]); | ||
const amount = ethers.utils.parseUnits(params.amountIn, decimals); | ||
if (amount.gt(decodedPolicy.maxAmount)) { | ||
throw new Error( | ||
`Amount exceeds policy limit. Max allowed: ${ethers.utils.formatUnits(decodedPolicy.maxAmount, decimals)}` | ||
); | ||
} | ||
if (amount.gt(balance)) { | ||
throw new Error( | ||
`Insufficient balance. PKP balance: ${ethers.utils.formatUnits(balance, decimals)}. Required: ${ethers.utils.formatUnits(amount, decimals)}` | ||
); | ||
} | ||
const gasData = await Lit.Actions.runOnce( | ||
{ waitForResponse: true, name: "gasPriceGetter" }, | ||
async () => { | ||
const provider = new ethers.providers.JsonRpcProvider(chainInfo.rpcUrl); | ||
const baseFeeHistory = await provider.send("eth_feeHistory", ["0x1", "latest", []]); | ||
const baseFee = ethers.BigNumber.from(baseFeeHistory.baseFeePerGas[0]); | ||
const nonce2 = await provider.getTransactionCount(pkp.ethAddress); | ||
const priorityFee = baseFee.div(4); | ||
const maxFee = baseFee.mul(2); | ||
return JSON.stringify({ | ||
maxFeePerGas: maxFee.toHexString(), | ||
maxPriorityFeePerGas: priorityFee.toHexString(), | ||
nonce: nonce2 | ||
}); | ||
} | ||
); | ||
const parsedGasData = JSON.parse(gasData); | ||
const { maxFeePerGas, maxPriorityFeePerGas, nonce } = parsedGasData; | ||
let estimatedGasLimit; | ||
try { | ||
estimatedGasLimit = await tokenContract.estimateGas.transfer( | ||
params.recipientAddress, | ||
amount, | ||
{ from: pkp.ethAddress } | ||
); | ||
console.log("Estimated gas limit:", estimatedGasLimit.toString()); | ||
estimatedGasLimit = estimatedGasLimit.mul(120).div(100); | ||
} catch (error) { | ||
console.error("Could not estimate gas. Using fallback gas limit of 100000.", error); | ||
estimatedGasLimit = ethers.BigNumber.from("100000"); | ||
} | ||
const transferTx = { | ||
to: params.tokenIn, | ||
data: tokenInterface.encodeFunctionData("transfer", [ | ||
params.recipientAddress, | ||
amount | ||
]), | ||
value: "0x0", | ||
gasLimit: estimatedGasLimit.toHexString(), | ||
maxFeePerGas, | ||
maxPriorityFeePerGas, | ||
nonce, | ||
chainId: chainInfo.chainId, | ||
type: 2 | ||
}; | ||
console.log("Signing transfer..."); | ||
const transferSig = await Lit.Actions.signAndCombineEcdsa({ | ||
toSign: ethers.utils.arrayify( | ||
ethers.utils.keccak256(ethers.utils.serializeTransaction(transferTx)) | ||
), | ||
publicKey: pkp.publicKey, | ||
sigName: "erc20TransferSig" | ||
}); | ||
const signedTransferTx = ethers.utils.serializeTransaction( | ||
transferTx, | ||
ethers.utils.joinSignature({ | ||
r: "0x" + JSON.parse(transferSig).r.substring(2), | ||
s: "0x" + JSON.parse(transferSig).s, | ||
v: JSON.parse(transferSig).v | ||
}) | ||
); | ||
console.log("Broadcasting transfer..."); | ||
const transferHash = await Lit.Actions.runOnce( | ||
{ waitForResponse: true, name: "txnSender" }, | ||
async () => { | ||
try { | ||
const provider = new ethers.providers.JsonRpcProvider(chainInfo.rpcUrl); | ||
const receipt = await provider.sendTransaction(signedTransferTx); | ||
return receipt.hash; | ||
} catch (error) { | ||
console.error("Error sending transfer:", error); | ||
throw error; | ||
} | ||
} | ||
); | ||
if (!ethers.utils.isHexString(transferHash)) { | ||
throw new Error(`Invalid transaction hash: ${transferHash}`); | ||
} | ||
Lit.Actions.setResponse({ | ||
response: JSON.stringify({ | ||
status: "success", | ||
transferHash | ||
}) | ||
}); | ||
} catch (error) { | ||
console.error("Error:", error); | ||
Lit.Actions.setResponse({ | ||
response: JSON.stringify({ | ||
status: "error", | ||
error: error.message | ||
}) | ||
}); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* Type definition for the SendERC20 Policy | ||
* This matches the Solidity struct: | ||
* struct SendERC20Policy { | ||
* uint256 maxAmount; | ||
* address[] allowedTokens; | ||
* address[] allowedRecipients; | ||
* } | ||
*/ | ||
export interface SendERC20Policy { | ||
maxAmount: string; | ||
allowedTokens: string[]; | ||
allowedRecipients: string[]; | ||
} | ||
/** | ||
* Schema for the SendERC20 Policy, used for CLI prompts | ||
*/ | ||
export declare const sendERC20PolicySchema: { | ||
type: string; | ||
properties: { | ||
maxAmount: { | ||
type: string; | ||
description: string; | ||
example: string; | ||
}; | ||
allowedTokens: { | ||
type: string; | ||
items: { | ||
type: string; | ||
pattern: string; | ||
description: string; | ||
}; | ||
description: string; | ||
example: string[]; | ||
}; | ||
allowedRecipients: { | ||
type: string; | ||
items: { | ||
type: string; | ||
pattern: string; | ||
description: string; | ||
}; | ||
description: string; | ||
example: string[]; | ||
}; | ||
}; | ||
required: string[]; | ||
}; | ||
/** | ||
* Validates and encodes a SendERC20Policy into the format expected by the Lit Action | ||
*/ | ||
export declare function encodeSendERC20Policy(policy: SendERC20Policy): string; | ||
/** | ||
* Decodes an ABI encoded SendERC20 policy | ||
*/ | ||
export declare function decodeSendERC20Policy(encodedPolicy: string): SendERC20Policy; |
Oops, something went wrong.