Skip to content

Commit

Permalink
Add scripts for creating and claiming gifts
Browse files Browse the repository at this point in the history
  • Loading branch information
delaaxe committed Aug 7, 2024
1 parent 4ab474c commit 0c580cd
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 59 deletions.
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

0 comments on commit 0c580cd

Please sign in to comment.