Skip to main content
There are several types of wallets and wallet behaviors that compose supports.

Smart Wallets

If you just need to interact with smart contracts, then the easiest thing to do is just build one of our smart wallets. You’ll be able to see all of your wallets in the dashboard at https://app.goldsky.com/dashboard/compose/{appName}.

create a smart wallet

/// <reference types="../.compose/types.d.ts" />

export async function main(
  { evm }: TaskContext,
  _args: any
) {
  // this is itempotent so the wallet is only created the first time this is called and is "retrieved" after that.
  // passing no args will create a "default" wallet for your app
  const wallet = await evm.wallet();
  console.log(wallet.address);

  // you can create multiple named wallets too.  This will generate multiple saved smart wallets that can be referenced by name in different 
  // tasks and task runs
  const walletOne = await evm.wallet({ name: "wallet-one" });
  const walletTwo = await evm.wallet({ name: "wallet-two" });

  // private key wallet
  const walletTwo = await evm.wallet({ privateKey: env.MY_KEY });

  // now you can use these wallet to make transactions (see below for details)
}

Using wallets

Once you have a wallet created you can use it to write to smart contracts, see the full smart contract docs for more details
/// <reference types="../../.compose/types.d.ts" /> 

//see smart contract docs for generating typed interfaces to contracts
import { BitcoinOracleContract } from "./.compose/generated";

export async function main(
  { evm, env }: TaskContext,
  _args: any
) {
  const wallet = await evm.wallet();

  const response = await fetch<{ bitcoin: { usd: number } }>(
    "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd"
  );

  // Convert timestamp and price to bytes32 format
  const timestampAsBytes32 = `0x${timestamp.toString(16).padStart(64, '0')}`;
  const priceAsBytes32 = `0x${Math.round(bitcoinPrice * 100).toString(16).padStart(64, '0')}`;

  const bitcoinOracleContract = new BitcoinOracleContract(env.ORACLE_ADDRESS, wallet);
  const { hash } = await bitcoinOracleContract.writeMethod.write(timestampAsBytes32, priceAsBytes32, { reorgDepth: 200, confirmationDepth: 5 });
}

You can also make or simulate transactions with methods on the Wallet class:
/// <reference types="../../.compose/types.d.ts" />

export async function main(
  { evm, env }: TaskContext,
  _args: any
) {
  const wallet = await evm.wallet();

  const { hash } = await wallet.writeContract(
    evm.chains.polygon,
    env.CONTRACT_ADDRESS,
    "reportPayouts(bytes32,uint256[])",
    [resultId, payouts],
    { 
      confirmations: 3, // this will not resolve the promise until the transaction has been seen in 3 blocks
      onReorg: {
        // this will replay the transaction with new nonce and new gas if it's reorged later on after the three confirmations have passed
        // see "Reorg Handling" for more info
        type: "replay", 
      }
    }
  );
}

EOA wallets

You can also use EOS that you already own, this allows you to self-fund gas, send and receive tokens in your tasks, and interact with smart contracts in which an EOA you already own has privileges on particular contract methods. Currently we support storing your EOA private key in Compose’s [secret management system], but in the future you’ll be able to use private keys that are secured within TEEs. EOA wallets never pass their private keys outside of the task process and they sign requests passed in unsigned from the host process. When tasks run in TEEs they’ll be able to use private keys very securely within the TEE, never exposing it to any part of the stack ouside of the TEE. This will empower Compose to run the most security sensitive use cases. First, you’ll need to store the private key in Goldsky’s secret management system and reference it in your compose.yaml file. You can see details on how to do that in the Secrets docs. Once you have your private key secret stored, you can use it to create a wallet:
/// <reference types="../.compose/types.d.ts" />

export async function main(
  { evm, env }: TaskContext,
  _args: any
) {
  const privateKeyWallet = await evm.wallet({ privateKey: env.MY_PRIVATE_KEY_SECRET });

  // now you can use the wallet the same as any other wallet
}

Gas sponsoring

By default, all wallets will use our gas sponsoring, allowing you to not think about and manage funding wallets for gas. However you can always choose to self-fund gas through configuration. When you use gas sponsoring you just pay the “gas bill” as part of your normal monthly goldsky bill, avoiding the need to deal with the complex budgetary and tax issues of purchasing gas tokens. When you don’t use gas sponsoring, you’ll need to get your wallet address from the compose dashboard at https://app.goldsky.com/dashboard/compose/{appName} and then transfer gas tokens to that wallet through a wallet or an exchange.

Override default gas sponsoring behavior

/// <reference types="../../.compose/types.d.ts" />

export async function main(
  { evm, env }: TaskContext,
  _args: any
) {
  // override the default behavior for a smart wallet
  const wallet = await evm.wallet({ name: "self-funded-wallet", sponsorGas: false });

  // override the default behavior for gas sponsoring EOA wallets
  const privateKeyWallet = await evm.wallet({ privateKey: env.MY_PRIVATE_KEY_SECRET, sponsorGas: false });
}

Full wallet interface

interface WalletConfig {
  name?: string; // defaults to "default"
  privateKey?: string;
  sponsorGas?: boolean; // defaults to true if no privateKey and false if privateKey
}

type ReplayOnReorg = {
  type: "replay";
};

type LogOnReorg = {
  type: "log";
  logLevel?: "error" | "info" | "warn"; // defaults to "error"
};

type Custom = {
  type: "custom",
  // your task will be sent with a payload the full transaction minus gas and nonce
  task: string; 
}

type OnReorgOptions = ReplayOnReorg | LogOnReorg | Custom;

export interface TransactionConfirmation {
  // this the number of block confirmations before we resolve the promise
  // i.e. "wait 5 blocks before proceeding to the next step in my task"
  confirmations?: number; 
  onReorg?: {
    action: OnReorgOptions;
    depth: number;
  };
}

interface IWallet {
  readonly name: string;
  writeContract<Args = unknown[]>(
    chain: Chain,
    contractAddress: string,
    functionSig: string,
    args: Args,
    confirmation?: TransactionConfirmation,
    retryConfig?: HostFunctionRetryConfig
  ): Promise<{ hash: string }>;
  readContract<Args = unknown[]>(
    chain: Chain,
    contractAddress: string,
    functionSig: string,
    args: Args,
    retryConfig?: HostFunctionRetryConfig
  ): Promise<{ hash: string }>;
  simulate: (
    chain: Chain,
    contractAddress: string,
    functionSig: string,
    args: unknown[],
    retryConfig?: HostFunctionRetryConfig
  ) => Promise<unknown>;
  transfer(
    chain: Chain,
    amount: string;
    recipient: string;
  ): Promise<{ hash: string }>;
}

type Wallet = (config: WalletConfig) => Promise<IWallet>;