diff --git a/docs/examples/arc-agent-sdk-example.ts b/docs/examples/arc-agent-sdk-example.ts new file mode 100644 index 0000000..15609c8 --- /dev/null +++ b/docs/examples/arc-agent-sdk-example.ts @@ -0,0 +1,102 @@ +/** + * Arc Agent SDK Example + * TypeScript SDK for Arc Testnet Agent Economy contracts + * + * npm: @consumeobeydie/arc-agent-sdk + * github: https://github.com/consumeobeydie/arc-agent-sdk + * + * Contracts (Arc Testnet): + * AgentIdentity: 0x5275783cD74eC21739Af8f3be9c42C024F671cFb + * SpendingLimits: 0x615a4B25448980a6b518f9F9088C206387535192 + * SplitPayment: 0x775D4DF117f0B63a16ade4185bDa221Adcb4AEA3 + * EventLogger: 0x9C50765e591663ED541B2fB863626f39fC6C12e0 + * ArcDEX: 0x1A142DF560a671c66c361A29a48Ab839Bc9F890E + */ + +import { ArcAgentSDK, ARC_TESTNET, CONTRACTS } from "@consumeobeydie/arc-agent-sdk"; + +async function main() { + // Initialize SDK with private key + const sdk = new ArcAgentSDK(process.env.PRIVATE_KEY!); + + console.log("Arc Agent SDK Example"); + console.log("===================="); + console.log(`Chain: ${ARC_TESTNET.name} (${ARC_TESTNET.id})`); + console.log(`Address: ${sdk.address}`); + + // ── 1. Register Agent Identity (ERC-8004) ──────────────────────── + console.log("\n1. Registering agent identity..."); + const { hash: registerHash } = await sdk.registerAgent( + "Arc Agent A", + "https://arc-intelligence-dashboard.vercel.app/agents/arc-agent-a", + "payments,vault,missions,usdc-transfer" + ); + console.log(` TX: ${registerHash}`); + + // ── 2. Setup Spending Limits ────────────────────────────────────── + console.log("\n2. Setting spending limits..."); + const { hash: limitHash } = await sdk.setSpendingLimit( + sdk.address, + 1_000_000_000_000_000_000n, // 1 USDC daily + 5_000_000_000_000_000_000n, // 5 USDC weekly + 100_000_000_000_000_000n // 0.1 USDC per tx + ); + console.log(` TX: ${limitHash}`); + + // ── 3. Check Budget ─────────────────────────────────────────────── + console.log("\n3. Checking budget..."); + const { ok, reason } = await sdk.canSpend(sdk.address, 10_000_000_000_000_000n); + console.log(` canSpend: ${ok} | reason: ${reason}`); + + const remaining = await sdk.remainingDaily(sdk.address); + console.log(` remainingDaily: ${Number(remaining) / 1e18} USDC`); + + // ── 4. Create Revenue Split ─────────────────────────────────────── + console.log("\n4. Creating revenue split (70% agent, 30% treasury)..."); + const TREASURY = "0x54b4B44749a95070560509B6Ec0be501665CcF63"; + const { hash: splitHash } = await sdk.createSplit( + "Agent Mission Revenue", + [sdk.address, TREASURY], + [7000n, 3000n], + ["agent-a", "treasury"] + ); + console.log(` TX: ${splitHash}`); + + // ── 5. Record Mission + Memo ────────────────────────────────────── + console.log("\n5. Recording mission with on-chain memo..."); + const { hash: memoHash } = await sdk.logWithMemo( + "Mission complete: API purchase", + "invoice-2026-001", + "agent=arc-agent-a,amount=0.01,status=success" + ); + console.log(` TX: ${memoHash}`); + + // ── 6. Record Mission in AgentIdentity ─────────────────────────── + console.log("\n6. Recording mission in AgentIdentity..."); + const agentId = await sdk.getAgentId(sdk.address); + const { hash: missionHash } = await sdk.recordMission(agentId, true); + console.log(` TX: ${missionHash}`); + + // ── 7. Check Success Rate ───────────────────────────────────────── + console.log("\n7. Checking agent success rate..."); + const successRate = await sdk.getSuccessRate(agentId); + console.log(` Success rate: ${successRate}%`); + + // ── 8. Record Spend ─────────────────────────────────────────────── + console.log("\n8. Recording spend..."); + const { hash: spendHash } = await sdk.recordSpend( + sdk.address, + 10_000_000_000_000_000n // 0.01 USDC + ); + console.log(` TX: ${spendHash}`); + const remainingAfter = await sdk.remainingDaily(sdk.address); + console.log(` Remaining after spend: ${Number(remainingAfter) / 1e18} USDC`); + + console.log("\n✅ Arc Agent SDK Example Complete!"); + console.log("\nContracts used:"); + Object.entries(CONTRACTS).forEach(([name, addr]) => { + console.log(` ${name}: ${addr}`); + }); +} + +main().catch(console.error); diff --git a/docs/examples/transaction-memo-integration.md b/docs/examples/transaction-memo-integration.md new file mode 100644 index 0000000..919af46 --- /dev/null +++ b/docs/examples/transaction-memo-integration.md @@ -0,0 +1,93 @@ +# Arc Transaction Memos: Multi-Agent Orchestrator Integration Example + +This example shows how to integrate Arc's Transaction Memos feature with an existing contract without modifying it, using the predeployed Memo contract on Arc Testnet. + +## Overview + +Transaction Memos (announced June 18, 2026) let you attach structured context to any contract call via a predeployed Memo contract, which routes the inner call through the CallFrom precompile so the original msg.sender is preserved. The Memo event is only emitted if the inner call succeeds, giving downstream systems a clean reconciliation signal. + +## Predeployed Contracts on Arc Testnet + +| Contract | Address | +|----------|---------| +| Memo | 0x5294E9927c3306DcBaDb03fe70b92e01cCede505 | +| Multicall3From | 0x522fAf9A91c41c443c66765030741e4AaCe147D0 | + +## Function Signature + +function callWithMemo(address target, bytes calldata data, bytes32 correlationId, string calldata memo) external returns (bool success, bytes memory result) + +Selector: 0xc3b2c4f8 + +This signature was derived by inspecting the Memo contract bytecode dispatcher and decoding a real on-chain transaction calldata to confirm parameter types, since the ABI was not yet published when this example was built. + +## Common Pitfalls + +- Empty revert data: behavior with cast call --from may differ from an actual sent transaction. +- Inner call must succeed: the Memo event only emits when the wrapped call succeeds. +- bytes32 correlationId must be exactly 32 bytes. + +## Live Verified Example + +Tested against the MultiAgentOrchestrator contract from arc-multi-agent: + +- Memo contract: 0x5294E9927c3306DcBaDb03fe70b92e01cCede505 +- Target: 0xe81f5BA4181eA29061C3C229c8D6EB4cFE56639C +- Tx: https://testnet.arcscan.app/tx/0x7532ce470169e2db2be43e5f9c43dd523d21e9071c04268ff5941f8ca839ef7b +- Status: Success, confirmed in 0.58 seconds + +## Resources + +- Arc Transaction Memos announcement: https://community.arc.io/home/blogs/arc-transaction-memos-structured-transaction-context-for-financial-workflows-on-arc-2026-06-18 +- Arc Contract Addresses: https://docs.arc.io/arc/references/contract-addresses +- Full implementation: https://github.com/consumeobeydie/arc-agent-api + +## Known Limitations + +### EOA-only Constraint + +The Memo contract is **EOA-only**. This is enforced at the precompile level, not in the Memo contract itself. + +When `memo()` is called, it internally invokes the `callFrom` precompile at `0x1800000000000000000000000000000000000003`. The precompile requires that `msg.sender` equals `tx.origin` — in other words, the caller must be an Externally Owned Account (EOA), not a smart contract. + +**What fails:** + +If you call `memo()` from a smart contract account — such as a Circle Developer Controlled Wallet, a Modular Wallet (ERC-4337 SCA), or any other contract-based account — the transaction will revert silently. The `MemoFailed` error is **not** raised in this case; the entire call reverts at the precompile level before the Memo contract can catch it. + +This was confirmed on Arc Testnet (Chain ID: 5042002) using the Circle Developer Controlled Wallets SDK. Gas estimation also fails for this reason when using `callFrom` simulation with contract-based senders. + +**What works:** + +Call `memo()` directly from an EOA wallet using a private key signer: + +```typescript +// ✅ Works — EOA signer via viem +import { createWalletClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; + +const account = privateKeyToAccount("0x..."); +const walletClient = createWalletClient({ account, transport: http(RPC_URL) }); + +await walletClient.writeContract({ + address: MEMO_CONTRACT, + abi: MEMO_ABI, + functionName: "memo", + args: [target, calldata, memoId, memoData], +}); +``` + +```typescript +// ❌ Fails — Circle Developer Controlled Wallet (Smart Contract Account) +// The callFrom precompile rejects contract callers silently. +// Gas estimation will also fail with this setup. +``` + +**Reference:** + +From [`IMemo.sol`](../../../contracts/src/memo/IMemo.sol): + +> EOA-only: `memo()` invokes the `callFrom` precompile, which requires the sender argument (`msg.sender` of Memo) to equal the precompile caller or `tx.origin`. A contract caller is neither, so `callFrom` reverts and the entire call reverts without raising `MemoFailed`. + +From [`ICallFrom.sol`](../../../contracts/src/call-from/ICallFrom.sol): + +> Executes a call to `target` with `data` as if `sender` were the caller.