-
-
Save thor-wong/2438c0e3970e22c75f4302ac2d75ac1b to your computer and use it in GitHub Desktop.
KITE Stablecoin Gasless Transfer Service Demo
This file contains hidden or 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
| import { ethers } from "ethers"; | |
| import { randomBytes } from "crypto"; | |
| import * as dotenv from "dotenv"; | |
| dotenv.config(); | |
| const RPC_URL = process.env.MAINNET_RPC_URL ?? "https://rpc.gokite.ai"; | |
| const BACKEND_URL = process.env.MAINNET_BACKEND_URL ?? "https://gasless.gokite.ai/mainnet"; | |
| const CONTRACT_ADDRESS = process.env.MAINNET_CONTRACT_ADDRESS ?? "0x7aB6f3ed87C42eF0aDb67Ed95090f8bF5240149e"; | |
| const USER_PRIVATE_KEY = process.env.USER_PRIVATE_KEY!; | |
| const TRANSFER_TO = process.env.MAINNET_TRANSFER_TO ?? "0x2Ba214b0AfCa4Cad5A6A9b8AF05032cE574F16e6"; // defaults to user address if unset | |
| const EIP712_NAME = process.env.MAINNET_EIP712_NAME ?? "Bridged USDC (Kite AI)"; | |
| const EIP712_VERSION = process.env.MAINNET_EIP712_VERSION ?? "2"; | |
| const TRANSFER_VALUE = "10000" | |
| function requireEnv(value: string, name: string): string { | |
| if (!value) { | |
| throw new Error(`${name} must be set`); | |
| } | |
| return value; | |
| } | |
| async function main() { | |
| const rpcUrl = requireEnv(RPC_URL, "MAINNET_RPC_URL"); | |
| const contractAddress = requireEnv(CONTRACT_ADDRESS, "MAINNET_CONTRACT_ADDRESS"); | |
| const userKey = requireEnv(USER_PRIVATE_KEY, "MAINNET_USER_PRIVATE_KEY"); | |
| const provider = new ethers.JsonRpcProvider(rpcUrl); | |
| const userWallet = new ethers.Wallet(userKey, provider); | |
| const toAddress = TRANSFER_TO ?? userWallet.address; | |
| const latest = await provider.getBlock("latest"); | |
| if (!latest) throw new Error("cannot fetch latest block"); | |
| const latest_block_now = BigInt(latest.timestamp); | |
| const now = BigInt(Math.floor(Date.now() / 1000)); | |
| const domain = { | |
| name: EIP712_NAME, | |
| version: EIP712_VERSION, | |
| chainId: (await provider.getNetwork()).chainId, | |
| verifyingContract: contractAddress | |
| }; | |
| const types = { | |
| TransferWithAuthorization: [ | |
| { name: "from", type: "address" }, | |
| { name: "to", type: "address" }, | |
| { name: "value", type: "uint256" }, | |
| { name: "validAfter", type: "uint256" }, | |
| { name: "validBefore", type: "uint256" }, | |
| { name: "nonce", type: "bytes32" } | |
| ] | |
| }; | |
| const validAfter = latest_block_now - 1n; | |
| const validBefore = now + 25n; | |
| const buildPayload = async () => { | |
| const message = { | |
| from: userWallet.address, | |
| to: toAddress, | |
| value: TRANSFER_VALUE, | |
| validAfter, | |
| validBefore, | |
| nonce: `0x${randomBytes(32).toString("hex")}` | |
| }; | |
| const signature = await userWallet.signTypedData(domain, types, message); | |
| const sig = ethers.Signature.from(signature); | |
| return { | |
| from: message.from, | |
| to: message.to, | |
| value: message.value.toString(), | |
| validAfter: message.validAfter.toString(), | |
| validBefore: message.validBefore.toString(), | |
| tokenAddress: contractAddress, | |
| nonce: message.nonce, | |
| v: sig.v, | |
| r: sig.r, | |
| s: sig.s | |
| }; | |
| }; | |
| console.log("Submitting to backend:", BACKEND_URL); | |
| console.log("From:", userWallet.address); | |
| console.log("To:", toAddress); | |
| console.log("Value:", TRANSFER_VALUE.toString()); | |
| console.log("EIP712 domain:", EIP712_NAME, EIP712_VERSION); | |
| const payload = await buildPayload(); | |
| console.log("Request payload:", JSON.stringify(payload, null, 2)); | |
| const res = await fetch(BACKEND_URL, { | |
| method: "POST", | |
| body: JSON.stringify(payload) | |
| }); | |
| const body = await res.json().catch(() => ({})); | |
| if (!res.ok) { | |
| console.error("Submit failed", res.status, body); | |
| process.exit(1); | |
| } | |
| console.log("Submit success:", body); | |
| if (body?.txHash) { | |
| console.log("Fetching receipt..."); | |
| const receipt = await provider.waitForTransaction(body.txHash); | |
| if (!receipt) { | |
| console.log("Receipt not found."); | |
| return; | |
| } | |
| const statusLabel = receipt.status === 1 ? "success" : "failed"; | |
| console.log(`Receipt: from=${receipt.from}, status=${statusLabel}`); | |
| } | |
| } | |
| main().catch((err) => { | |
| console.error(err); | |
| process.exit(1); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment