Skip to main content

Proxy Wallet Service & External Contract Interactions

The Sphere Proxy Wallet Service provides advanced capabilities for developers who need direct control over transaction signing, wallet instance management, and interaction with external smart contracts. This service is ideal for building sophisticated DeFi applications, custom smart contract integrations, and applications requiring fine-grained transaction control.

This page covers:

  • Getting wallet instance information for specific chains
  • Signing transactions without broadcasting them
  • Signing and broadcasting transactions to the network
  • Signing arbitrary messages
  • Working with external smart contracts

Prerequisites

Before using the Proxy Wallet Service, ensure:

  1. User Authentication: The user must be authenticated through the Sphere SDK.
  2. SDK Authenticated: Your Sphere SDK instance must be authenticated with the user's accessToken.
  3. Understanding of Blockchain Transactions: This service provides low-level access to transaction signing and requires understanding of blockchain transaction structures.

Refer to Creating & Accessing User Wallets or OTP Verification & Session Management for details on establishing an authenticated SDK session.

import Sphere, {
Environment,
ChainName,
GetWalletInstanceRequest,
WalletInstanceResponse,
SignTransactionRequest,
SignTransactionResponse,
SendTransactionRequest,
SendTransactionResponse,
SignMessageRequest,
SignMessageResponse,
} from "@stratosphere-network/wallet";

const sphere = new Sphere({
apiKey: "YOUR_API_KEY", // Your project API key
environment: Environment.DEVELOPMENT,
});

// Example: After user login, you get an accessToken
// sphere.setBearerToken("USER_ACCESS_TOKEN");

1. Getting Wallet Instance (sphere.proxyWallet.getWalletInstance)

This method returns comprehensive wallet information for a specific chain, including the wallet address, public key, provider details, and available methods. This is useful when you need to understand the wallet's capabilities or integrate with external libraries that require wallet instance information.

Intuition: This is like getting a complete profile of your wallet for a specific blockchain network. It tells you your address, what provider you're connected to, what methods are available, and other technical details needed for advanced integrations.

Flow:

  1. Your application specifies the chain for which it needs wallet information.
  2. It calls sphere.proxyWallet.getWalletInstance() with the chain parameter.
  3. The Sphere SDK communicates with the Stratosphere Network to retrieve the wallet instance details.
  4. A response is returned with complete wallet information including address, public key, provider info, and available methods.

Diagram: Getting Wallet Instance Information

SDK Usage: Getting Wallet Instance

async function getWalletInstance(
chainName: ChainName
): Promise<WalletInstanceResponse | null> {
if (!sphere.isAuthenticated()) {
console.error("SDK is not authenticated. Please log in the user first.");
return null;
}

try {
const requestPayload: GetWalletInstanceRequest = {
chain: chainName,
};

console.log(`Getting wallet instance for ${chainName}...`);
const response = await sphere.proxyWallet.getWalletInstance(requestPayload);

if (response) {
console.log("Wallet instance retrieved successfully!");
console.log(" Address:", response.address);
console.log(" Public Key:", response.publicKey);
console.log(" Chain:", response.chain.name);
console.log(" Provider:", response.provider.name);
console.log(
" Available Methods:",
Object.keys(response.availableMethods)
);
return response;
} else {
console.error("Failed to get wallet instance. Response was empty.");
return null;
}
} catch (error) {
console.error("Error getting wallet instance:", error);
throw error;
}
}

// Example Usage:
/*
getWalletInstance(ChainName.ETHEREUM)
.then(walletInstance => {
if (walletInstance) {
console.log(`Ethereum wallet address: ${walletInstance.address}`);
console.log(`Chain ID: ${walletInstance.chain.id}`);
console.log(`Native token: ${walletInstance.chain.nativeToken}`);
// Use wallet instance for external contract interactions
}
})
.catch(err => console.error("Get wallet instance failed:", err));
*/

GetWalletInstanceRequest Parameters:

  • chain: ChainName: The blockchain network for which to retrieve wallet instance information.

WalletInstanceResponse Structure:

interface WalletInstanceResponse {
address: string; // The wallet address on the specified chain
publicKey: string; // The public key associated with the wallet
provider: ProviderInfo; // Provider connection details
chain: ChainInfo; // Chain-specific information
_isWallet: boolean; // Internal flag indicating this is a wallet instance
_isSigner: boolean; // Internal flag indicating signing capabilities
availableMethods: {
getAddress: string;
getBalance: string;
sendTransaction: string;
signTransaction: string;
signMessage: string;
}; // Available method endpoints
}

interface ChainInfo {
id: number; // Chain ID (e.g., 1 for Ethereum mainnet)
name: string; // Human-readable chain name
nativeToken: string; // Native token symbol (e.g., "ETH", "MATIC")
tokens: string[]; // List of supported token symbols on this chain
}

interface ProviderInfo {
url: string; // RPC endpoint URL
chainId: number; // Chain ID for the provider
name: string; // Provider name
}

2. Signing Transactions (sphere.proxyWallet.signTransaction)

This method allows you to sign a transaction without broadcasting it to the network. This is useful when you need to prepare transactions for later submission, batch multiple transactions, or integrate with external systems that handle transaction broadcasting.

Intuition: This is like writing and signing a check but not handing it over yet. You prepare all the transaction details, cryptographically sign them to prove authorization, but the transaction isn't sent to the blockchain until you choose to broadcast it separately.

Flow:

  1. Your application prepares transaction data (recipient, value, gas parameters, etc.).
  2. It calls sphere.proxyWallet.signTransaction() with the transaction details.
  3. The Sphere SDK communicates with the Stratosphere Network to sign the transaction using the user's private key.
  4. A response is returned with the signed transaction hex string, transaction hash, and original transaction data.

Diagram: Signing a Transaction

SDK Usage: Signing Transactions

async function signTransaction(
chainName: ChainName,
transactionData: {
to?: string;
value?: string;
data?: string;
gasLimit?: string;
gasPrice?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
nonce?: number;
type?: number;
chainId?: number;
}
): Promise<SignTransactionResponse | null> {
if (!sphere.isAuthenticated()) {
console.error("SDK is not authenticated. Please log in the user first.");
return null;
}

try {
const requestPayload: SignTransactionRequest = {
chain: chainName,
transactionData: transactionData,
};

console.log(`Signing transaction on ${chainName}...`);
console.log("Transaction data:", transactionData);

const response = await sphere.proxyWallet.signTransaction(requestPayload);

if (response) {
console.log("Transaction signed successfully!");
console.log(" Transaction Hash:", response.txHash);
console.log(" From Address:", response.from);
console.log(
" Signed Transaction Length:",
response.signedTransaction.length
);
return response;
} else {
console.error("Failed to sign transaction. Response was empty.");
return null;
}
} catch (error) {
console.error("Error signing transaction:", error);
throw error;
}
}

// Example Usage: Signing a token transfer
/*
const transferData = {
to: "0xRecipientAddress",
value: "0", // For token transfers, ETH value is usually 0
data: "0xa9059cbb000000000000000000000000recipientaddress0000000000000000000000000000000000000000000000000de0b6b3a7640000", // ERC-20 transfer calldata
gasLimit: "21000",
gasPrice: "20000000000", // 20 Gwei
type: 2, // EIP-1559 transaction
chainId: 1 // Ethereum mainnet
};

signTransaction(ChainName.ETHEREUM, transferData)
.then(signedTx => {
if (signedTx) {
console.log("Signed transaction:", signedTx.signedTransaction);
// You can now broadcast this transaction using your preferred method
}
})
.catch(err => console.error("Sign transaction failed:", err));
*/

SignTransactionRequest Parameters:

  • chain: ChainName: The blockchain network on which to sign the transaction.
  • transactionData: object: The transaction parameters:
    • to?: string: Recipient address (for contract calls, this would be the contract address).
    • value?: string: Amount of native token to send (in wei for Ethereum).
    • data?: string: Encoded function call data for smart contract interactions.
    • gasLimit?: string: Maximum gas units this transaction can consume.
    • gasPrice?: string: Gas price in wei (for legacy transactions).
    • maxFeePerGas?: string: Maximum total fee per gas (for EIP-1559 transactions).
    • maxPriorityFeePerGas?: string: Maximum priority fee per gas (for EIP-1559 transactions).
    • nonce?: number: Transaction nonce (leave undefined to auto-calculate).
    • type?: number: Transaction type (0 = legacy, 2 = EIP-1559).
    • chainId?: number: Chain ID for the target network.

SignTransactionResponse Structure:

interface SignTransactionResponse {
signedTransaction: string; // Hex-encoded signed transaction ready for broadcast
txHash: string; // Transaction hash (computed from signed transaction)
from: string; // Address that signed the transaction
originalTx: SignTransactionRequest["transactionData"]; // Original transaction data
}

3. Sending Transactions (sphere.proxyWallet.sendTransaction)

This method signs and immediately broadcasts a transaction to the blockchain network. Unlike the regular sphere.transactions.send() method which processes transactions as gasless, this method gives you full control over gas parameters and transaction structure.

Intuition: This is like writing a check and immediately cashing it. You prepare the transaction details, sign them, and immediately submit them to the blockchain network. You have full control over gas fees and transaction parameters.

Flow:

  1. Your application prepares transaction data with complete gas parameters.
  2. It calls sphere.proxyWallet.sendTransaction() with the transaction details.
  3. The Sphere SDK communicates with the Stratosphere Network to sign and broadcast the transaction.
  4. A response is returned with the transaction receipt including hash, confirmation details, and blockchain information.

Diagram: Sending a Transaction

SDK Usage: Sending Transactions

async function sendTransaction(
chainName: ChainName,
transactionData: {
to?: string;
value?: string;
data?: string;
gasLimit?: string;
gasPrice?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
nonce?: number;
type?: number;
chainId?: number;
}
): Promise<SendTransactionResponse | null> {
if (!sphere.isAuthenticated()) {
console.error("SDK is not authenticated. Please log in the user first.");
return null;
}

try {
const requestPayload: SendTransactionRequest = {
chain: chainName,
transactionData: transactionData,
};

console.log(`Sending transaction on ${chainName}...`);
console.log("Transaction data:", transactionData);

const response = await sphere.proxyWallet.sendTransaction(requestPayload);

if (response) {
console.log("Transaction sent successfully!");
console.log(" Transaction Hash:", response.hash);
console.log(" From Address:", response.from);
console.log(" Block Number:", response.blockNumber);
console.log(" Confirmations:", response.confirmations);
return response;
} else {
console.error("Failed to send transaction. Response was empty.");
return null;
}
} catch (error) {
console.error("Error sending transaction:", error);
throw error;
}
}

// Example Usage: Sending ETH
/*
const ethTransferData = {
to: "0xRecipientAddress",
value: "1000000000000000000", // 1 ETH in wei
gasLimit: "21000",
gasPrice: "20000000000", // 20 Gwei
type: 0, // Legacy transaction
chainId: 1 // Ethereum mainnet
};

sendTransaction(ChainName.ETHEREUM, ethTransferData)
.then(txReceipt => {
if (txReceipt) {
console.log("ETH sent! Transaction hash:", txReceipt.hash);
// Wait for confirmations or update UI
}
})
.catch(err => console.error("Send transaction failed:", err));
*/

// Example Usage: Interacting with a smart contract
/*
const contractInteractionData = {
to: "0xContractAddress",
value: "0", // No ETH being sent to contract
data: "0xa9059cbb000000000000000000000000recipientaddress0000000000000000000000000000000000000000000000000de0b6b3a7640000", // ERC-20 transfer function call
gasLimit: "65000",
maxFeePerGas: "30000000000", // 30 Gwei
maxPriorityFeePerGas: "2000000000", // 2 Gwei tip
type: 2, // EIP-1559 transaction
chainId: 1
};

sendTransaction(ChainName.ETHEREUM, contractInteractionData)
.then(txReceipt => {
if (txReceipt) {
console.log("Contract interaction successful:", txReceipt.hash);
}
})
.catch(err => console.error("Contract interaction failed:", err));
*/

SendTransactionRequest Parameters:

The parameters are identical to SignTransactionRequest:

  • chain: ChainName: The blockchain network on which to send the transaction.
  • transactionData: object: Complete transaction parameters (same as signing).

SendTransactionResponse Structure:

interface SendTransactionResponse {
hash: string; // Transaction hash on the blockchain
to?: string; // Recipient address
from: string; // Sender address
value?: string; // Amount transferred (in wei for Ethereum)
gasLimit?: string; // Gas limit used
gasPrice?: string; // Gas price used
nonce: number; // Transaction nonce
chainId?: number; // Chain ID
blockNumber?: number; // Block number (when mined)
blockHash?: string; // Block hash (when mined)
timestamp?: number; // Block timestamp
confirmations: number; // Number of confirmations
raw: any; // Raw transaction object from the blockchain
}

4. Signing Messages (sphere.proxyWallet.signMessage)

This method allows you to cryptographically sign arbitrary messages using the user's private key. This is commonly used for authentication, proving ownership of an address, or creating signatures for off-chain verification.

Intuition: This is like signing a document to prove it came from you. The message can be any text, and the signature proves that the owner of the wallet address created it. Others can verify that the signature came from your address without needing your private key.

Flow:

  1. Your application prepares a message to be signed.
  2. It calls sphere.proxyWallet.signMessage() with the message and chain.
  3. The Sphere SDK communicates with the Stratosphere Network to sign the message using the user's private key.
  4. A response is returned with the signature, original message, signer address, and verification data.

Diagram: Signing a Message

SDK Usage: Signing Messages

async function signMessage(
chainName: ChainName,
message: string
): Promise<SignMessageResponse | null> {
if (!sphere.isAuthenticated()) {
console.error("SDK is not authenticated. Please log in the user first.");
return null;
}

try {
const requestPayload: SignMessageRequest = {
chain: chainName,
message: message,
};

console.log(`Signing message on ${chainName}...`);
console.log("Message:", message);

const response = await sphere.proxyWallet.signMessage(requestPayload);

if (response) {
console.log("Message signed successfully!");
console.log(" Signature:", response.signature);
console.log(" Signer Address:", response.signer);
console.log(" Message Hash:", response.messageHash);
console.log(" Recovered Address:", response.recoveredAddress);
return response;
} else {
console.error("Failed to sign message. Response was empty.");
return null;
}
} catch (error) {
console.error("Error signing message:", error);
throw error;
}
}

// Example Usage: Authentication challenge
/*
const authMessage = `Welcome to MyApp!

Click to sign in and accept the MyApp Terms of Service: https://myapp.com/tos

This request will not trigger a blockchain transaction or cost any gas fees.

Wallet address:
0x...

Nonce: ${Date.now()}`;

signMessage(ChainName.ETHEREUM, authMessage)
.then(signature => {
if (signature) {
console.log("Authentication signature:", signature.signature);
// Send signature to your backend for verification
// Your backend can use libraries like ethers.js to verify:
// ethers.utils.verifyMessage(message, signature) === expectedAddress
}
})
.catch(err => console.error("Message signing failed:", err));
*/

// Example Usage: Prove ownership
/*
const ownershipMessage = `I am the owner of this wallet address.
Timestamp: ${new Date().toISOString()}
Domain: myapp.com`;

signMessage(ChainName.POLYGON, ownershipMessage)
.then(signature => {
if (signature) {
console.log("Ownership proof:", {
message: signature.message,
signature: signature.signature,
address: signature.signer
});
// Store or transmit this proof
}
})
.catch(err => console.error("Ownership proof failed:", err));
*/

SignMessageRequest Parameters:

  • chain: ChainName: The blockchain network context for signing (affects address format).
  • message: string: The arbitrary message to be signed.

SignMessageResponse Structure:

interface SignMessageResponse {
signature: string; // Hex-encoded signature
message: string; // Original message that was signed
signer: string; // Address of the wallet that signed the message
messageHash: string; // Hash of the message (used in signing process)
recoveredAddress: string; // Address recovered from signature (should match signer)
}

Advanced Use Cases & Examples

Smart Contract Interaction

The Proxy Wallet Service is particularly powerful for interacting with smart contracts. Here's an example of how to interact with an ERC-20 token contract:

// Example: Approve tokens for a DEX
async function approveTokens(
tokenContractAddress: string,
spenderAddress: string,
amount: string,
chainName: ChainName
) {
// ERC-20 approve function signature: approve(address,uint256)
const approveSelector = "0x095ea7b3";

// Encode the parameters (spender address and amount)
const paddedSpender = spenderAddress.slice(2).padStart(64, "0");
const paddedAmount = BigInt(amount).toString(16).padStart(64, "0");

const data = `${approveSelector}${paddedSpender}${paddedAmount}`;

const transactionData = {
to: tokenContractAddress,
value: "0", // No ETH being sent
data: data,
gasLimit: "60000",
maxFeePerGas: "30000000000", // 30 Gwei
maxPriorityFeePerGas: "2000000000", // 2 Gwei
type: 2,
};

return await sendTransaction(chainName, transactionData);
}

// Example: Query contract state (read-only)
async function queryContract(
contractAddress: string,
functionData: string,
chainName: ChainName
) {
// For read-only operations, you might need to use other methods
// or integrate with external libraries like ethers.js using the wallet instance
const walletInstance = await getWalletInstance(chainName);

if (walletInstance) {
// Use the provider information to make read calls
console.log(
"Contract query can be performed using provider:",
walletInstance.provider.url
);
}
}

Batch Operations

You can use the signing functionality to prepare multiple transactions for batch submission:

async function prepareBatchTransactions(
transactions: Array<{
to: string;
value: string;
data?: string;
}>,
chainName: ChainName
) {
const signedTransactions = [];

for (let i = 0; i < transactions.length; i++) {
const tx = transactions[i];
const transactionData = {
...tx,
gasLimit: "21000",
gasPrice: "20000000000",
nonce: undefined, // Let the service handle nonce calculation
};

const signedTx = await signTransaction(chainName, transactionData);
if (signedTx) {
signedTransactions.push(signedTx);
}
}

return signedTransactions;
}

Integration with External Libraries

The wallet instance information can be used to integrate with popular blockchain libraries:

// Example integration with ethers.js (conceptual)
async function getEthersProvider(chainName: ChainName) {
const walletInstance = await getWalletInstance(chainName);

if (walletInstance) {
// You could create an ethers provider using the RPC URL
// const provider = new ethers.providers.JsonRpcProvider(walletInstance.provider.url);

// For contract interactions, you'd need to create a custom signer
// that uses the Sphere SDK for actual signing
console.log("Provider URL for ethers.js:", walletInstance.provider.url);
console.log("Wallet address:", walletInstance.address);
}
}

Error Handling

The Proxy Wallet Service can return specific errors. Here's how to handle them:

async function handleProxyWalletErrors() {
try {
const result = await sphere.proxyWallet.getWalletInstance({
chain: ChainName.ETHEREUM,
});
} catch (error: any) {
if (error.supportedChains) {
console.error(
"Chain not supported. Supported chains:",
error.supportedChains
);
} else {
console.error("Proxy wallet error:", error.error || error.message);
}
}
}

Important Considerations

  • Authentication Required: All Proxy Wallet methods require user authentication.
  • Gas Management: Unlike regular Sphere transactions, you're responsible for setting appropriate gas parameters.
  • Transaction Costs: You pay network gas fees directly for these transactions (not gasless).
  • Advanced Usage: This service is designed for developers who need direct blockchain access and understand transaction mechanics.
  • Security: Message signing should be used carefully - only sign messages you understand and trust.
  • Nonce Management: The service can handle nonce calculation automatically, or you can specify nonces manually for advanced use cases.

Next Steps


For comprehensive details about transaction structure, gas optimization, and smart contract interaction patterns, consult the Sphere SDK API reference or join our developer community.