Skip to content

Date.now() Error in Zoe Invitation Creation #12226

@pynchmeister

Description

@pynchmeister

Describe the bug

When using publicInvitationMaker with wallet.makeOffer(), Zoe internally calls Date.now() during invitation ID generation, causing a TypeError: secure mode Calling %SharedDate%.now() throws error in the SES environment. This prevents offers from executing despite successful transaction submission.

Impact: Offers fail silently - transaction is submitted (fee charged), but funds are not transferred. This completely blocks contract execution when using dynamic invitation creation patterns.

To Reproduce

1. Create a contract with public invitation maker:

// qstn-survey.contract.js
import { Far } from '@endo/far';
import { atomicRearrange } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
import '@agoric/zoe/exported.js';

export const start = async zcf => {
  const istBrand = zcf.getTerms().brands.IST;
  const proceeds = zcf.makeEmptySeatKit().zcfSeat;

  const fundSurveyHandler = funderSeat => {
    const { give } = funderSeat.getProposal();
    const fundingAmount = give.IST;
    
    atomicRearrange(
      zcf,
      harden([[funderSeat, proceeds, { IST: fundingAmount }]]),
    );
    
    funderSeat.exit(true);
    return undefined;
  };

  const makeSurveyFundingInvitation = () => {
    return zcf.makeInvitation(
      fundSurveyHandler,
      'fund survey',
      undefined,
      undefined,
    );
  };

  const publicFacet = Far('Public Facet', {
    makeSurveyFundingInvitation,
  });

  return harden({ publicFacet });
};

2. Deploy contract to chain:

# Using agoric-3-proposals Docker image
docker compose up -d
cd contract && npm start

3. In frontend, submit offer using wallet.makeOffer():

import { makeAgoricWalletConnection } from '@agoric/wallet-connection';

const wallet = await makeAgoricWalletConnection(watcher, chainStorageWatcher);
const istAmount = AmountMath.make(brands.IST, 1_000_000n); // 1 IST

// This triggers the error:
await wallet.makeOffer(
  {
    source: 'contract',
    instance: contractInstance,
    publicInvitationMaker: 'makeSurveyFundingInvitation',
  },
  {
    give: { IST: istAmount },
    want: {},
  },
  undefined,
  (update) => console.log('Status:', update)
);

4. Observe the error:

In chain logs:

SwingSet: vat: v43: wallet <address> IMMEDIATE OFFER ERROR: (RemoteTypeError(error:liveSlots:v9#70082)#156)

SwingSet: vat: v43: wallet <address> offerStatus { 
  error: 'TypeError: secure mode Calling %SharedDate%.now() throws',
  id: 1763091046542,
  invitationSpec: {
    instance: Object [Alleged: InstanceHandle] {},
    publicInvitationMaker: 'makeSurveyFundingInvitation',
    source: 'contract'
  },
  proposal: {
    give: { IST: { brand: Object [Alleged: IST brand] {}, value: 1_000_000n } },
    want: {}
  }
}

Result:

  • ✅ Transaction submitted successfully
  • ✅ Transaction fee charged (~0.2 IST)
  • ❌ Offer fails with Date.now() error
  • ❌ Funds NOT transferred (balance unchanged except for fee)

Expected behavior

When wallet.makeOffer() is called with publicInvitationMaker:

  1. Frontend calls wallet.makeOffer() with invitation specification
  2. Wallet requests contract to create invitation via publicInvitationMaker
  3. Contract creates invitation using zcf.makeInvitation() without Date.now() errors
  4. Invitation is serialized and passed to wallet
  5. Offer is executed and funds are transferred

Expected result:

  • Transaction submitted
  • Fee charged
  • Offer executes successfully
  • Funds transferred to contract

Platform Environment

  • OS: macOS (darwin 23.6.0)
  • Node.js: v21.7.2
  • Docker: Version 28.5.1
  • Agoric SDK Version: 0.35.0-u22.1 (from ghcr.io/agoric/agoric-3-proposals:latest)
  • Chain: Local devnet (agoriclocal)
  • Wallet: Keplr
  • Frontend Library: @agoric/wallet-connection

Verification:

$ docker exec $(docker ps -q -f name=agd) agd version
0.35.0-u22.1

Special Notes:

  • Using Docker-based local chain
  • Contract has zero Date.now() calls (verified)
  • Contract has zero console.* calls (verified)
  • Error occurs even with minimal invitation pattern (no parameters, no customDetails)

Additional context

Root Cause

The error occurs inside Zoe's internal invitation creation code, not in application code. When zcf.makeInvitation() is called, Zoe internally generates a unique invitation ID using Date.now(), which violates SES (Secure EcmaScript) rules that prohibit non-deterministic operations.

Evidence

I've confirmed this is a framework issue, not application code:

  • ✅ Contract code has zero Date.now() calls (grep verified)
  • ✅ Contract code has zero console.* calls (grep verified)
  • ✅ Simplified invitation creation to absolute minimum - still fails
  • ✅ Removed all customDetails and proposalShape - still fails
  • ✅ Matched exact pattern from working dapp-offer-up example - still fails
  • ✅ Used empty callback functions - still fails
  • ✅ Removed invitationArgs from frontend - still fails

Error occurs regardless of:

  • Invitation parameters
  • Custom details
  • Proposal shapes
  • Callback functions
  • Frontend patterns

Full Error Stack

2025-11-14T03:30:49.763Z SwingSet: vat: v43: TypeError: secure mode Calling %SharedDate%.now() throws
 at makeError (/bundled-source/.../ses/src/error/assert.js:352)
 at decodeErrorCommon (/bundled-source/.../marshal/src/marshal.js:309)

2025-11-14T03:30:49.765Z SwingSet: vat: v43: wallet <address> offerStatus { 
  error: 'TypeError: secure mode Calling %SharedDate%.now() throws',
  id: 1763091046542,
  invitationSpec: {
    instance: Object [Alleged: InstanceHandle] {},
    invitationArgs: [ [ 'osmosis' ], null, null ],
    publicInvitationMaker: 'makeSurveyFundingInvitation',
    source: 'contract'
  },
  offerArgs: undefined,
  proposal: {
    give: { IST: { brand: Object [Alleged: IST brand] {}, value: 1_000_000n } },
    want: {}
  }
}

2025-11-14T03:30:49.783Z SwingSet: xsnap: v43: RemoteTypeError(error:liveSlots:v9#70082)#156
2025-11-14T03:30:49.798Z SwingSet: xsnap: v10: UnhandledPromiseRejectionWarning: (RemoteTypeError(error:liveSlots:v43#70078)#88)

Attempted Workarounds

All of the following were attempted without success:

  1. Remove Date.now() from contract - No effect (contract doesn't use it)
  2. Simplify invitation creation - Still fails
  3. Remove invitationArgs - Still fails
  4. Use empty callbacks - Prevents callback errors but offer still fails
  5. Match dapp-offer-up pattern exactly - Still fails
  6. Remove all customDetails/proposalShape - Still fails

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions