import { playgroundHelper } from "../../helpers/helpers"; import { stablecoinVaultConstants } from "../constants"; import { MPCSignature } from "chainsig.js"; import { Schema, serialize } from "borsh"; import { PublicKey } from "@near-js/crypto"; import { baseEncode } from "@near-js/utils"; import { sha256 } from "js-sha256"; // NEAR Intents Solver Relay API integration // Uses the official solver-relay-v2 API for intent submission const SOLVER_RELAY_API = "https://solver-relay-v2.chaindefuser.com/rpc"; // Quote parameters for easy modification const QUOTE_ASSET_IN = stablecoinVaultConstants.BASE_TOKEN_ID; const QUOTE_ASSET_OUT = stablecoinVaultConstants.ETH_USDC_TOKEN_ID; const QUOTE_AMOUNT_IN = "799990000"; // 1 USDC (6 decimals) // Helper function to get asset quotes from NEAR Intents API async function getAssetQuote( assetIn: string, assetOut: string, exactAmountIn: string ) { const response = await fetch(SOLVER_RELAY_API, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ jsonrpc: "2.0", method: "quote", params: [ { defuse_asset_identifier_in: assetIn, defuse_asset_identifier_out: assetOut, exact_amount_in: exactAmountIn, min_deadline_ms: 120000, // 2 mins }, ], id: 1, }), }); const data = await response.json(); console.log(data); if (data.error) { throw new Error(`Quote error: ${data.error.message}`); } // Return the first quote from the array return data.result[0]; } /* { "id": 1, "jsonrpc": "2.0", "method": "publish_intent", "params": [ { "quote_hashes": ["00000000000000000000000000000000", ...], "signed_data": { "standard": "nep413", "payload": { "message": "{\"signer_id\":\"user.near\",\"deadline\":\"2024-10-14T12:53:40.000Z\",\"intents\":[{\"intent\":\"token_diff\",\"diff\":{\"nep141:ft1.near\":\"300\",\"nep141:ft2.near\":\"-500\"}},{\"intent\":\"transfer\",\"receiver_id\":\"referral.near\",\"tokens\":{\"nep141:ft1.near\":\"1\"}},{\"intent\":\"ft_withdraw\",\"token\":\"ft1.near\",\"receiver_id\":\"ft1.near\",\"amount\":\"299\",\"memo\":\"WITHDRAW_TO:address_on_target_chain\"}]}", "nonce": "bacFZfjWD8lm4mwAZ/TScL8HrrapeXlTSyAeD4i8Lfs=", "recipient": "intents.near" }, "signature": "ed25519:2yJ1ANYAL1yRoXk8uiDZygyH3TeRpVucwBMpUh1bsvcCLL3BBoJzqAojQNN4mxz9v5fSzbwqz7p9MFtZKNKW81Cg", "public_key": "ed25519:4vyWshm6BE4uoHk7fot2iij7tFXrjWp4wDnNEJx2W4sf" } } ] } */ // Function to publish intent to NEAR Intents API async function publishIntent( intentPayload: any, signature: string, publicKey: string, quoteHashes: string[] ) { console.log("publishIntent args:", { intentPayload, signature, publicKey, quoteHashes, }); console.log("📝 Publishing intent to NEAR Intents API..."); const publishResponse = await fetch(SOLVER_RELAY_API, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ jsonrpc: "2.0", method: "publish_intent", params: [ { quote_hashes: quoteHashes, signed_data: { standard: "nep413", payload: { message: intentPayload.message, nonce: Buffer.from(intentPayload.nonce).toString("base64"), recipient: intentPayload.recipient, }, signature: `ed25519:${signature}`, public_key: publicKey, }, }, ], id: 2, }), }); const publishData = await publishResponse.json(); console.log(publishData); if (publishData.error) { throw new Error(`Intent publishing failed: ${publishData.error.message}`); } const intentHash = publishData.result.intent_hash; console.log("🚀 Intent published successfully!"); console.log("📋 Intent hash:", intentHash); return intentHash; } // Function to sign intent using vault's chain signature capability async function signIntentWithChainSig(intentPayload: any) { console.log("Signing intent using vault's chain signature capability..."); const signatures = await playgroundHelper.account.callFunction< MPCSignature[] >({ contractId: playgroundHelper.vaultContractId, methodName: "propose_execution", args: { policy_id: "sign_near_intents_message", function_args: JSON.stringify(intentPayload), deadline_hours: 0, }, gas: 50000000000000n, // 50 TGas }); const firstSignResult = signatures[0]; console.log(signatures); if (!("signature" in firstSignResult)) { throw new Error("Invalid signature result from chain signature"); } console.log("Chain signature obtained, verifying..."); return firstSignResult; } /* { "id": 1, "jsonrpc": "2.0", "method": "get_status", "params": [ { "intent_hash": "00000000000000000000000000000000" } ] } */ // Function to check intent status on NEAR Intents API async function checkIntentStatus(hash: string) { const statusResponse = await fetch(SOLVER_RELAY_API, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ jsonrpc: "2.0", method: "get_status", params: [ { intent_hash: hash, }, ], id: 3, }), }); const statusData = await statusResponse.json(); if (statusData.error) { console.warn(`Status check failed: ${statusData.error.message}`); return null; } return statusData.result; } // Define NEP-413 message schema for Borsh serialization const MessageSchema: Schema = { struct: { tag: "u32", message: "string", nonce: { array: { type: "u8", len: 32, }, }, recipient: "string", callbackUrl: { option: "string", }, }, }; interface INep413PayloadToSign { message: string; nonce: Buffer; recipient: string; callbackUrl?: string; } class Message { tag: number; message: string; nonce: Buffer; recipient: string; callbackUrl?: string; constructor(data: INep413PayloadToSign) { this.tag = 2147484061; // NEP-413 tag this.message = data.message; this.nonce = data.nonce; this.recipient = data.recipient; if (data.callbackUrl) { this.callbackUrl = data.callbackUrl; } } } // Sign NEAR Intents message payload using NEP-413 standard // This creates a cryptographically signed intent that can be published to NEAR Intents API async function main() { console.log("Initiating NEAR Intents cross-chain swap..."); // Get quote for USDC to WETH swap using existing constants console.log("Getting asset quote from solver relay..."); const quote = await getAssetQuote( QUOTE_ASSET_IN, QUOTE_ASSET_OUT, QUOTE_AMOUNT_IN ); console.log("Received quote:", quote); // Generate random nonce for message uniqueness let nonceBuffer = Buffer.alloc(32); nonceBuffer = crypto.getRandomValues(nonceBuffer); console.log(nonceBuffer); console.log("Creating NEAR Intent payload..."); // Message to sign - Fixed to match NEP-413 format const intentPayload = { nonce: [...nonceBuffer], recipient: "intents.near", message: JSON.stringify({ signer_id: stablecoinVaultConstants.NEAR_CHAINSIG_DERIVED_ACCOUNT, deadline: quote.expiration_time, intents: [ { intent: "token_diff", diff: { [quote.defuse_asset_identifier_in]: `-${quote.amount_in}`, // Send quoted amount (negative = outgoing) [quote.defuse_asset_identifier_out]: quote.amount_out, // Receive quoted amount }, }, ], }), }; console.log("Intent payload created"); // Sign the intent payload using vault's chain signature capability const firstSignResult = await signIntentWithChainSig(intentPayload); const signatureBuffer = Buffer.from(firstSignResult.signature); // Serialize message for verification const hashedPayload = serialize( MessageSchema, new Message({ ...intentPayload, nonce: Buffer.from(intentPayload.nonce), }) ); // Verify signature - convert hex implicit account to public key const publicKeyHex = stablecoinVaultConstants.NEAR_CHAINSIG_DERIVED_ACCOUNT; const publicKeyBytes = Buffer.from(publicKeyHex, "hex"); const publicKey = new PublicKey({ keyType: 0, data: new Uint8Array(publicKeyBytes), }); const isVerified = publicKey.verify( new Uint8Array(sha256.array(hashedPayload)), signatureBuffer ); console.log("Signature verification result:", { isVerified }); if (isVerified) { console.log("NEAR Intent successfully signed!"); // Publish intent to the solver relay API const intentHash = await publishIntent( intentPayload, baseEncode(Buffer.from(firstSignResult.signature)), publicKey.toString(), quote.quote_hash ? [quote.quote_hash] : [] ); // Check initial status const status = await checkIntentStatus(intentHash); if (status) { console.log("📊 Intent status:", status.status); console.log("📝 Intent details:", status); } console.log( "Intent successfully submitted to NEAR Intents for cross-chain execution!" ); } else { throw new Error("Signature verification failed!"); } } // Execute main function main() .then(() => { console.log("NEAR Intents submission completed successfully"); }) .catch((error) => { console.error("Failed to submit NEAR Intent:", error.message); process.exit(1); });