Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scripts for creating and claiming gifts #65

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ For more details about how to trigger it please see the `claimInternal` function

It is also possible for someone else to pay for the claim fees. This can be useful if the funds deposited to pay for the claim transaction are not enough, or if someone wants to subsidize the claim.

The receiver can use the private key sign a message containing the address receiving the address (and optionally some address that will receive the dust). Using this signature, anybody can execute a transaction to perform the claim. To do so, they should call `claim_external` on the escrow account (through the `execute_action` entrypoint).
The receiver can use the private key sign a message containing the receiving address (and optionally some address that will receive the dust). Using this signature, anybody can execute a transaction to perform the claim. To do so, they should call `claim_external` on the escrow account (through the `execute_action` entrypoint).

![Sessions diagram](/docs/external_claim.png)

Expand Down Expand Up @@ -87,9 +87,9 @@ The parameters are as follow:
## Local development

We recommend you to install scarb through ASDF. Please refer to [these instructions](https://docs.swmansion.com/scarb/download.html#install-via-asdf).
Thanks to the [.tool-versions file](./.tool-versions), you don't need to install a specific scarb or starknet foundry version. The correct one will be automatically downloaded and installed.
Thanks to the [.tool-versions file](./.tool-versions), you can install the correct versions for scarb and starknet-foundry by running `asdf install`.

##@ Test the contracts (Cairo)
### Test the contracts (Cairo)

```
scarb test
Expand Down
Binary file added bun.lockb
Binary file not shown.
25 changes: 15 additions & 10 deletions lib/claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Call,
CallData,
Calldata,
ProviderInterface,
RPC,
TransactionReceipt,
UniversalDetails,
Expand Down Expand Up @@ -146,19 +147,18 @@ export async function claimInternal(args: {
gift: Gift;
receiver: string;
giftPrivateKey: string;
provider?: ProviderInterface;
overrides?: { escrowAccountAddress?: string; callToAddress?: string };
details?: UniversalDetails;
}): Promise<TransactionReceipt> {
const escrowAddress = args.overrides?.escrowAccountAddress || calculateEscrowAddress(args.gift);
const escrowAccount = getEscrowAccount(args.gift, args.giftPrivateKey, escrowAddress);
const escrowAccount = getEscrowAccount(args.gift, args.giftPrivateKey, escrowAddress, args.provider);
const response = await escrowAccount.execute(
[
{
contractAddress: args.overrides?.callToAddress ?? escrowAddress,
calldata: [buildGiftCallData(args.gift), args.receiver],
entrypoint: "claim_internal",
},
],
{
contractAddress: args.overrides?.callToAddress ?? escrowAddress,
calldata: [buildGiftCallData(args.gift), args.receiver],
entrypoint: "claim_internal",
},
undefined,
{ ...args.details },
);
Expand Down Expand Up @@ -200,9 +200,14 @@ export const randomReceiver = (): string => {
return `0x${encode.buf2hex(ec.starkCurve.utils.randomPrivateKey())}`;
};

export function getEscrowAccount(gift: Gift, giftPrivateKey: string, forceEscrowAddress?: string): Account {
export function getEscrowAccount(
gift: Gift,
giftPrivateKey: string,
forceEscrowAddress?: string,
provider?: ProviderInterface,
): Account {
return new Account(
manager,
provider ?? manager,
forceEscrowAddress || num.toHex(calculateEscrowAddress(gift)),
giftPrivateKey,
undefined,
Expand Down
140 changes: 97 additions & 43 deletions lib/deposit.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,62 @@
import { Account, Call, CallData, Contract, InvokeFunctionResponse, TransactionReceipt, hash, uint256 } from "starknet";
import { AccountConstructorArguments, Gift, LegacyStarknetKeyPair, deployer, manager } from "./";
import { AccountConstructorArguments, Gift, LegacyStarknetKeyPair, deployer, manager } from ".";

export const STRK_GIFT_MAX_FEE = 200000000000000000n; // 0.2 STRK
export const STRK_GIFT_AMOUNT = STRK_GIFT_MAX_FEE + 1n;
export const ETH_GIFT_MAX_FEE = 200000000000000n; // 0.0002 ETH
export const ETH_GIFT_AMOUNT = ETH_GIFT_MAX_FEE + 1n;

const depositAbi = [
{
type: "function",
name: "deposit",
inputs: [
{
name: "escrow_class_hash",
type: "core::starknet::class_hash::ClassHash",
},
{
name: "gift_token",
type: "core::starknet::contract_address::ContractAddress",
},
{
name: "gift_amount",
type: "core::integer::u256",
},
{
name: "fee_token",
type: "core::starknet::contract_address::ContractAddress",
},
{
name: "fee_amount",
type: "core::integer::u128",
},
{
name: "gift_pubkey",
type: "core::felt252",
},
],
outputs: [],
state_mutability: "external",
},
];

const approveAbi = [
{
type: "function",
name: "approve",
inputs: [
{
name: "spender",
type: "core::starknet::contract_address::ContractAddress",
},
{ name: "amount", type: "core::integer::u256" },
],
outputs: [{ type: "core::bool" }],
state_mutability: "external",
},
];

export function getMaxFee(useTxV3: boolean): bigint {
return useTxV3 ? STRK_GIFT_MAX_FEE : ETH_GIFT_MAX_FEE;
}
Expand All @@ -14,42 +65,37 @@ export function getGiftAmount(useTxV3: boolean): bigint {
return useTxV3 ? STRK_GIFT_AMOUNT : ETH_GIFT_AMOUNT;
}

export async function deposit(depositParams: {
sender: Account;
interface DepositParams {
giftAmount: bigint;
feeAmount: bigint;
factoryAddress: string;
feeTokenAddress: string;
giftTokenAddress: string;
giftSignerPubKey: bigint;
overrides?: {
escrowAccountClassHash?: string;
};
}): Promise<{ response: InvokeFunctionResponse; gift: Gift }> {
const { sender, giftAmount, feeAmount, factoryAddress, feeTokenAddress, giftTokenAddress, giftSignerPubKey } =
depositParams;
const factory = await manager.loadContract(factoryAddress);
const feeToken = await manager.loadContract(feeTokenAddress);
const giftToken = await manager.loadContract(giftTokenAddress);
escrowAccountClassHash: string;
}

const escrowAccountClassHash =
depositParams.overrides?.escrowAccountClassHash || (await factory.get_latest_escrow_class_hash());
const gift: Gift = {
factory: factoryAddress,
escrow_class_hash: escrowAccountClassHash,
sender: deployer.address,
gift_token: giftTokenAddress,
gift_amount: giftAmount,
fee_token: feeTokenAddress,
fee_amount: feeAmount,
gift_pubkey: giftSignerPubKey,
};
const calls: Array<Call> = [];
export function createDeposit(
sender: string,
{
giftAmount,
feeAmount,
factoryAddress,
feeTokenAddress,
giftTokenAddress,
giftSignerPubKey,
escrowAccountClassHash,
}: DepositParams,
) {
const factory = new Contract(depositAbi, factoryAddress);
const feeToken = new Contract(approveAbi, feeTokenAddress);
const giftToken = new Contract(approveAbi, giftTokenAddress);
const calls: Call[] = [];
if (feeTokenAddress === giftTokenAddress) {
calls.push(feeToken.populateTransaction.approve(factory.address, giftAmount + feeAmount));
calls.push(feeToken.populateTransaction.approve(factoryAddress, giftAmount + feeAmount));
} else {
calls.push(feeToken.populateTransaction.approve(factory.address, feeAmount));
calls.push(giftToken.populateTransaction.approve(factory.address, giftAmount));
calls.push(feeToken.populateTransaction.approve(factoryAddress, feeAmount));
calls.push(giftToken.populateTransaction.approve(factoryAddress, giftAmount));
}
calls.push(
factory.populateTransaction.deposit(
Expand All @@ -61,10 +107,26 @@ export async function deposit(depositParams: {
giftSignerPubKey,
),
);
return {
response: await sender.execute(calls),
gift,
const gift: Gift = {
factory: factoryAddress,
escrow_class_hash: escrowAccountClassHash,
sender,
gift_token: giftTokenAddress,
gift_amount: giftAmount,
fee_token: feeTokenAddress,
fee_amount: feeAmount,
gift_pubkey: giftSignerPubKey,
};
return { calls, gift };
}

export async function deposit(
sender: Account,
depositParams: DepositParams,
): Promise<{ response: InvokeFunctionResponse; gift: Gift }> {
const { calls, gift } = createDeposit(sender.address, depositParams);
const response = await sender.execute(calls);
return { response, gift };
}

export async function defaultDepositTestSetup(args: {
Expand Down Expand Up @@ -97,15 +159,14 @@ export async function defaultDepositTestSetup(args: {
const giftSigner = new LegacyStarknetKeyPair(args.overrides?.giftPrivateKey);
const giftPubKey = giftSigner.publicKey;

const { response, gift } = await deposit({
sender: deployer,
overrides: { escrowAccountClassHash },
const { response, gift } = await deposit(deployer, {
giftAmount,
feeAmount,
factoryAddress: args.factory.address,
feeTokenAddress: feeToken.address,
giftTokenAddress,
giftSignerPubKey: giftPubKey,
escrowAccountClassHash,
});
const txReceipt = await manager.waitForTransaction(response.transaction_hash);
return { gift, giftPrivateKey: giftSigner.privateKey, txReceipt };
Expand All @@ -121,14 +182,7 @@ export function calculateEscrowAddress(gift: Gift): string {
gift_pubkey: gift.gift_pubkey,
};

const escrowAddress = hash.calculateContractAddressFromHash(
0,
gift.escrow_class_hash,
CallData.compile({
...constructorArgs,
gift_amount: uint256.bnToUint256(gift.gift_amount),
}),
gift.factory,
);
const calldata = CallData.compile({ ...constructorArgs, gift_amount: uint256.bnToUint256(gift.gift_amount) });
const escrowAddress = hash.calculateContractAddressFromHash(0, gift.escrow_class_hash, calldata, gift.factory);
return escrowAddress;
}
22 changes: 22 additions & 0 deletions scripts/claim_gift.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { constants, RpcProvider } from "starknet";
import { claimInternal } from "../lib";

/// To use this script, fill in the following 3 variables printed from scripts/create_gift.ts:

const gift = {
factory: "0x000000000000000000000000000000000000000000000000000000000000000",
escrow_class_hash: "0x000000000000000000000000000000000000000000000000000000000000000",
sender: "0x0000000000000000000000000000000000000000000000000000000000000000",
gift_token: "0x0000000000000000000000000000000000000000000000000000000000000000",
gift_amount: 69n,
fee_token: "0x0000000000000000000000000000000000000000000000000000000000000000",
fee_amount: 42n,
gift_pubkey: 100000000000000000000000000000000000000000000000000000000000000000000000000n,
};
const receiver = "0x0000000000000000000000000000000000000000000000000000000000000000";
const giftPrivateKey = "0x0000000000000000000000000000000000000000000000000000000000000000";

const provider = new RpcProvider({ nodeUrl: constants.NetworkName.SN_SEPOLIA });
const receipt = await claimInternal({ gift, receiver, giftPrivateKey, provider });

console.log("Tx hash:", receipt.transaction_hash);
36 changes: 36 additions & 0 deletions scripts/create_gift.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createDeposit, LegacyStarknetKeyPair } from "../lib";
import { logTransactionJson } from "./json_tx_builder";

/// To use this script, check the following value:

const factoryAddress = "0x42a18d85a621332f749947a96342ba682f08e499b9f1364325903a37c5def60";
const escrowAccountClassHash = "0x661aad3c9812f0dc0a78f320a58bdd8fed18ef601245c20e4bf43667bfd0289";
const ethAddress = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
const strkAddress = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";

if (!factoryAddress) {
throw new Error("Factory contract address is not set. Please set it in the script file.");
}

const giftSigner = new LegacyStarknetKeyPair();
const sender = "0x1111111111111111111111111111111111111111111111111111111111111111";
const receiver = "0x2222222222222222222222222222222222222222222222222222222222222222";

const { calls, gift } = createDeposit(sender, {
giftAmount: 1n, // 1 wei
feeAmount: 3n * 10n ** 18n, // 3 STRK
factoryAddress,
feeTokenAddress: strkAddress,
giftTokenAddress: ethAddress,
giftSignerPubKey: giftSigner.publicKey,
escrowAccountClassHash,
});

console.log();
console.log("const gift =", gift, ";");
console.log(`const receiver = "${receiver}";`);
console.log(`const giftPrivateKey = "${giftSigner.privateKey}";`);
console.log();

console.log("Calls:");
logTransactionJson(calls);
6 changes: 3 additions & 3 deletions tests-integration/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe("Test Core Factory Functions", function () {

it(`Pausable`, async function () {
// Deploy factory
const { factory } = await setupGiftProtocol();
const { factory, escrowAccountClassHash } = await setupGiftProtocol();
const receiver = randomReceiver();
const giftSigner = new LegacyStarknetKeyPair();

Expand All @@ -87,14 +87,14 @@ describe("Test Core Factory Functions", function () {
await manager.waitForTransaction(txHash1);

await expectRevertWithErrorMessage("Pausable: paused", async () => {
const { response } = await deposit({
sender: deployer,
const { response } = await deposit(deployer, {
giftAmount: ETH_GIFT_AMOUNT,
feeAmount: ETH_GIFT_MAX_FEE,
factoryAddress: factory.address,
feeTokenAddress: token.address,
giftTokenAddress: token.address,
giftSignerPubKey: giftSigner.publicKey,
escrowAccountClassHash,
});
return response;
});
Expand Down
Loading