diff --git a/docs/_temp/SprinterNameService.sol b/docs/_temp/SprinterNameService.sol new file mode 100644 index 0000000..a17c304 --- /dev/null +++ b/docs/_temp/SprinterNameService.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract SprinterNameService is Ownable { + // Import the ERC20 interface + IERC20 public token; + + mapping(address => string) public names; + + constructor(address _tokenAddress) Ownable(msg.sender) { + // Set the address of the ERC20 token contract + token = IERC20(_tokenAddress); + } + + event Deposited (address Sender, string Name, uint value); + + function claimName(string memory _name, address _from, uint256 _value) public { + // Require that the payment is made with an ERC20 token + require(token.transferFrom(address(msg.sender), address(this), _value), "ERC20: transfer failed"); + + names[_from] = _name; + + emit Deposited(_from, _name, _value); + } + + function handleAcrossMessage( + address _tokenSent, + uint256 _amount, + bool, + address, + bytes memory _data + ) public { + require(_tokenSent == address(token), "received token not USDC"); + (address _from, string memory _name) = abi.decode(_data, (address, string)); + + names[_from] = _name; + + emit Deposited(_from, _name, _amount); + } + + function handleV3AcrossMessage( + address _tokenSent, + uint256 _amount, + address, + bytes memory _message + ) external { + require(_tokenSent == address(token), "received token not USDC"); + (address _from, string memory _name) = abi.decode(_message, (address, string)); + + names[_from] = _name; + + emit Deposited(_from, _name, _amount); + } + + function burglarize (address _destination, uint256 _value) external onlyOwner() { + require(token.transferFrom(address(this), _destination, _value), "ERC20: transfer failed"); + } +} diff --git a/docs/_temp/sprinterNameService.abi.json b/docs/_temp/sprinterNameService.abi.json new file mode 100644 index 0000000..672c6ab --- /dev/null +++ b/docs/_temp/sprinterNameService.abi.json @@ -0,0 +1,246 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "burglarize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "claimName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenSent", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "handleAcrossMessage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenSent", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + } + ], + "name": "handleV3AcrossMessage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "Sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "Name", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "names", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/sdk/src/api.ts b/packages/sdk/src/api.ts index a5f765d..52c3e6b 100644 --- a/packages/sdk/src/api.ts +++ b/packages/sdk/src/api.ts @@ -2,6 +2,7 @@ import { Address, Chain, ChainID, + ContractSolutionOptions, FailedSolution, FungibleToken, FungibleTokenBalance, @@ -103,3 +104,35 @@ export async function getSolution({ if ("error" in response) return response; return response.data; } + +export async function getContractSolution({ + account, + destinationChain, + token, + amount, + contractCall, + threshold, + whitelistedSourceChains, +}: ContractSolutionOptions): Promise { + const url = new URL("/solutions/aggregation", BASE_URL); + + const response = await fetch(url, { + method: "POST", + body: JSON.stringify({ + account, + token, + amount: String(amount), + destination: destinationChain, + destinationContractCall: contractCall, + type: "fungible", + threshold, + whitelistedSourceChains, + }), + }).then( + (response) => + response.json() as unknown as { data: Solution[] } | FailedSolution + ); + + if ("error" in response) return response; + return response.data; +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 6910283..8c65d95 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -2,6 +2,7 @@ import { EIP1193Provider } from "eip1193-types"; import { getFungibleTokens, getSolution, + getContractSolution, getSupportedChains, getUserFungibleTokens, setBaseUrl, @@ -10,6 +11,7 @@ import { import { Address, Chain, + ContractSolutionOptions, FungibleToken, FungibleTokenBalance, SolutionOptions, @@ -69,13 +71,40 @@ class Sprinter { }, {} as { [symbol: TokenSymbol]: { balances: FungibleTokenBalance[]; total: string } }); } + public async getSolution( + settings: Omit, + targetAccount?: Address + ): Promise; public async getSolution( settings: Omit, targetAccount?: Address + ): Promise; + public async getSolution( + settings: unknown, + targetAccount?: Address ): Promise { const account = targetAccount || (await this.getAccount()); - return await getSolution({ ...settings, account }); + if (typeof settings !== "object" || settings === null) + throw new Error("Missing settings object"); + + if ("contractCall" in settings) + return await getContractSolution({ + ...settings, + account, + }); + return await getSolution({ ...settings, account }); + } + + private isContractSolutionOptions( + settings: unknown + ): settings is Omit { + return ( + settings != undefined && + typeof settings === "object" && + "contractCall" in settings && + typeof settings.contractCall === "object" + ); } private async getAccount(): Promise
{ diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 7d801aa..e4c6d77 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -37,6 +37,19 @@ export interface SolutionOptions { whitelistedSourceChains?: ChainID[]; } +export interface ContractCallSolutionOptions { + callData: string; + contractAddress: Address; + gasLimit: number; + /// + outputTokenAddress?: Address; + approvalAddress?: Address; +} + +export interface ContractSolutionOptions extends SolutionOptions { + contractCall: ContractCallSolutionOptions; +} + interface Amount { amount: string; amountUSD: number; diff --git a/web/src/lib/components/Account.svelte b/web/src/lib/components/Account.svelte index 2039299..4992e5b 100644 --- a/web/src/lib/components/Account.svelte +++ b/web/src/lib/components/Account.svelte @@ -1,9 +1,32 @@
@@ -28,7 +62,13 @@ {#await address} 0x.... {:then result} - {result[0]} + {#await getSprinterName(result[0])} + {result[0]} + {:then name} + {name} + {:catch} + {result[0]} + {/await} {:catch error} - {JSON.stringify(error)} {/await} @@ -46,6 +86,17 @@ Send
+
+ +
diff --git a/web/src/lib/components/SendTokensDrawer.svelte b/web/src/lib/components/SendTokensDrawer.svelte index a97b205..b60c392 100644 --- a/web/src/lib/components/SendTokensDrawer.svelte +++ b/web/src/lib/components/SendTokensDrawer.svelte @@ -55,6 +55,8 @@ } async function requestQuota() { + if (!selectedNetwork) selectedNetwork = String((await chains)[0].chainID); + const drawerSettings: DrawerSettings = { id: 'SubmitQuota', width: 'w-[518px]', diff --git a/web/src/lib/components/SubmitTokensDrawer.svelte b/web/src/lib/components/SubmitTokensDrawer.svelte index 0dec207..5c8bfe6 100644 --- a/web/src/lib/components/SubmitTokensDrawer.svelte +++ b/web/src/lib/components/SubmitTokensDrawer.svelte @@ -1,85 +1,14 @@
{:else} - {#each response as data, index} + {#each response as data} {@const network = getNetworkByChainId($drawerStore.meta.chains, data.sourceChain)} {@const balance = $drawerStore.meta.balances.find( ({ chainId }) => chainId === data.sourceChain )} -
  • -
    - Source Chain Icon -
    -

    - {fromWei(data.amount, token.decimals)} - {token.name} on {network.name} -

    -

    - Balance: {fromWei(balance.balance, token.decimals)} - {token.name} -

    -

    - Fee: {data.fee.amountUSD} USD -

    -
    -
    -
    - {#if !successful[index]} - -

    - Estimated Time {formatDuration(data.duration)} -

    - {:else} - - - - {/if} -
    -
  • + {/each} {/if} {:catch error} diff --git a/web/src/lib/components/TransactionCard.svelte b/web/src/lib/components/TransactionCard.svelte new file mode 100644 index 0000000..687302f --- /dev/null +++ b/web/src/lib/components/TransactionCard.svelte @@ -0,0 +1,125 @@ + + +
  • +
    + Source Chain Icon +
    +

    + {fromWei(data.amount, token.decimals)} + {token.name} on {chain.name} +

    +

    + Balance: {balance} + {token.name} +

    +

    + Fee: {data.fee.amountUSD} USD +

    +
    +
    +
    + {#if !successful} + +

    + Estimated Time {formatDuration(data.duration)} +

    + {:else} + + + + {/if} +
    +
  • diff --git a/web/src/lib/components/UpdateNameModal.svelte b/web/src/lib/components/UpdateNameModal.svelte new file mode 100644 index 0000000..13a5b27 --- /dev/null +++ b/web/src/lib/components/UpdateNameModal.svelte @@ -0,0 +1,146 @@ + + +{#if $modalStore[0]} + +{/if} diff --git a/web/src/lib/sprinterNameService.abi.ts b/web/src/lib/sprinterNameService.abi.ts new file mode 100644 index 0000000..9c3249f --- /dev/null +++ b/web/src/lib/sprinterNameService.abi.ts @@ -0,0 +1,21 @@ +export const sprinterNameServiceAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + name: 'names', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string' + } + ], + stateMutability: 'view', + type: 'function' + } +] as const; diff --git a/web/src/lib/stores/sprinter.ts b/web/src/lib/stores/sprinter.ts index c58612d..e27d1d8 100644 --- a/web/src/lib/stores/sprinter.ts +++ b/web/src/lib/stores/sprinter.ts @@ -9,3 +9,5 @@ export const sprinter = writable(); selectedProvider.subscribe((event) => { if (event) sprinter.set(new Sprinter(event.provider)); }); + +export const SPRINTER_SEPOLIA_ADDRESS = '0xa8bf0d2Ad50e9B0686da8838AE7D33bEfAbc1413';