feat(x402): add Cronos facilitator adapter and atomic purchase flow
This commit is contained in:
@@ -43,12 +43,17 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
mapping(bytes32 => AtomicTypes.AtomicCommitment) private _commitments;
|
mapping(bytes32 => AtomicTypes.AtomicCommitment) private _commitments;
|
||||||
mapping(bytes32 => AtomicTypes.AtomicObligation) private _obligations;
|
mapping(bytes32 => AtomicTypes.AtomicObligation) private _obligations;
|
||||||
mapping(bytes32 => address) private _refundRecipients;
|
mapping(bytes32 => address) private _refundRecipients;
|
||||||
|
mapping(bytes32 => bool) private _asyncDestinationCorridors;
|
||||||
|
|
||||||
event CorridorConfigured(bytes32 indexed corridorId, address indexed assetIn, address indexed assetOut);
|
event CorridorConfigured(bytes32 indexed corridorId, address indexed assetIn, address indexed assetOut);
|
||||||
|
event AsyncDestinationCorridorSet(bytes32 indexed corridorId, bool enabled);
|
||||||
event IntentCreated(
|
event IntentCreated(
|
||||||
bytes32 indexed obligationId, bytes32 indexed corridorId, bytes32 indexed intentId, address sender
|
bytes32 indexed obligationId, bytes32 indexed corridorId, bytes32 indexed intentId, address sender
|
||||||
);
|
);
|
||||||
event CommitmentAccepted(bytes32 indexed obligationId, address indexed fulfiller, uint256 bondAmount);
|
event CommitmentAccepted(bytes32 indexed obligationId, address indexed fulfiller, uint256 bondAmount);
|
||||||
|
event DestinationFulfillmentRecorded(
|
||||||
|
bytes32 indexed obligationId, bytes32 indexed destinationTxHash, bytes32 indexed invoiceHash, address fulfiller
|
||||||
|
);
|
||||||
event SettlementInitiated(
|
event SettlementInitiated(
|
||||||
bytes32 indexed obligationId, bytes32 indexed settlementId, bytes32 indexed settlementMode
|
bytes32 indexed obligationId, bytes32 indexed settlementId, bytes32 indexed settlementMode
|
||||||
);
|
);
|
||||||
@@ -69,6 +74,9 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
error MinimumReplenishNotMet();
|
error MinimumReplenishNotMet();
|
||||||
error ZeroPayer();
|
error ZeroPayer();
|
||||||
error ZeroRefundRecipient();
|
error ZeroRefundRecipient();
|
||||||
|
error AsyncDestinationProofRequired();
|
||||||
|
error NotAsyncDestinationCorridor();
|
||||||
|
error ZeroDestinationProof();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
address liquidityVault_,
|
address liquidityVault_,
|
||||||
@@ -110,6 +118,11 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
_corridors[corridorId].degraded = degraded;
|
_corridors[corridorId].degraded = degraded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAsyncDestinationCorridor(bytes32 corridorId, bool enabled) external onlyRole(CORRIDOR_MANAGER_ROLE) {
|
||||||
|
_asyncDestinationCorridors[corridorId] = enabled;
|
||||||
|
emit AsyncDestinationCorridorSet(corridorId, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
function createIntent(CreateIntentParams calldata p) external nonReentrant returns (bytes32 obligationId) {
|
function createIntent(CreateIntentParams calldata p) external nonReentrant returns (bytes32 obligationId) {
|
||||||
obligationId = _createIntent(p, msg.sender, msg.sender);
|
obligationId = _createIntent(p, msg.sender, msg.sender);
|
||||||
}
|
}
|
||||||
@@ -139,16 +152,19 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
}
|
}
|
||||||
if (p.amountIn > cfg.maxNotional) revert MaxNotionalExceeded();
|
if (p.amountIn > cfg.maxNotional) revert MaxNotionalExceeded();
|
||||||
|
|
||||||
AtomicTypes.CorridorLiquidityState memory state =
|
bool asyncDestination = _asyncDestinationCorridors[corridorId];
|
||||||
liquidityVault.getCorridorLiquidityState(corridorId, p.assetOut);
|
if (!asyncDestination) {
|
||||||
if (p.minAmountOut > state.freeLiquidity) revert ReservedLiquidityLimitExceeded();
|
AtomicTypes.CorridorLiquidityState memory state =
|
||||||
if (state.totalLiquidity > 0) {
|
liquidityVault.getCorridorLiquidityState(corridorId, p.assetOut);
|
||||||
uint256 nextReserved = state.reservedLiquidity + p.minAmountOut;
|
if (p.minAmountOut > state.freeLiquidity) revert ReservedLiquidityLimitExceeded();
|
||||||
if ((nextReserved * 10_000) / state.totalLiquidity > cfg.maxReservedBps) {
|
if (state.totalLiquidity > 0) {
|
||||||
revert ReservedLiquidityLimitExceeded();
|
uint256 nextReserved = state.reservedLiquidity + p.minAmountOut;
|
||||||
|
if ((nextReserved * 10_000) / state.totalLiquidity > cfg.maxReservedBps) {
|
||||||
|
revert ReservedLiquidityLimitExceeded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (state.settlementBacklog > cfg.maxSettlementBacklog) revert SettlementBacklogExceeded();
|
||||||
}
|
}
|
||||||
if (state.settlementBacklog > cfg.maxSettlementBacklog) revert SettlementBacklogExceeded();
|
|
||||||
|
|
||||||
bytes32 intentId = keccak256(
|
bytes32 intentId = keccak256(
|
||||||
abi.encode(
|
abi.encode(
|
||||||
@@ -181,12 +197,40 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
|
|
||||||
_refundRecipients[obligationId] = refundTo;
|
_refundRecipients[obligationId] = refundTo;
|
||||||
obligationEscrow.escrowFunds(obligationId, p.assetIn, payer, p.amountIn);
|
obligationEscrow.escrowFunds(obligationId, p.assetIn, payer, p.amountIn);
|
||||||
liquidityVault.reserveLiquidity(corridorId, p.assetOut, obligationId, p.minAmountOut);
|
if (!asyncDestination) {
|
||||||
|
liquidityVault.reserveLiquidity(corridorId, p.assetOut, obligationId, p.minAmountOut);
|
||||||
|
}
|
||||||
|
|
||||||
emit IntentCreated(obligationId, corridorId, intentId, payer);
|
emit IntentCreated(obligationId, corridorId, intentId, payer);
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitCommitment(bytes32 obligationId, bytes32 settlementMode) external nonReentrant {
|
function submitCommitment(bytes32 obligationId, bytes32 settlementMode) external nonReentrant {
|
||||||
|
AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
|
||||||
|
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
|
||||||
|
if (_asyncDestinationCorridors[corridorId]) revert AsyncDestinationProofRequired();
|
||||||
|
_submitCommitment(obligationId, settlementMode, msg.sender, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitAsyncDestinationFulfillment(
|
||||||
|
bytes32 obligationId,
|
||||||
|
bytes32 settlementMode,
|
||||||
|
bytes32 destinationTxHash,
|
||||||
|
bytes32 invoiceHash
|
||||||
|
) external nonReentrant {
|
||||||
|
if (destinationTxHash == bytes32(0)) revert ZeroDestinationProof();
|
||||||
|
AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
|
||||||
|
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
|
||||||
|
if (!_asyncDestinationCorridors[corridorId]) revert NotAsyncDestinationCorridor();
|
||||||
|
_submitCommitment(obligationId, settlementMode, msg.sender, false);
|
||||||
|
emit DestinationFulfillmentRecorded(obligationId, destinationTxHash, invoiceHash, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _submitCommitment(
|
||||||
|
bytes32 obligationId,
|
||||||
|
bytes32 settlementMode,
|
||||||
|
address fulfiller,
|
||||||
|
bool releaseLocalLiquidity
|
||||||
|
) internal {
|
||||||
AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
|
AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
|
||||||
AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
|
AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
|
||||||
if (obligation.status != AtomicTypes.ObligationStatus.IntentCreated) revert InvalidStatus();
|
if (obligation.status != AtomicTypes.ObligationStatus.IntentCreated) revert InvalidStatus();
|
||||||
@@ -195,21 +239,23 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
|
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
|
||||||
bytes32 mode = settlementMode == bytes32(0) ? _corridors[corridorId].defaultSettlementMode : settlementMode;
|
bytes32 mode = settlementMode == bytes32(0) ? _corridors[corridorId].defaultSettlementMode : settlementMode;
|
||||||
uint256 requiredBond = feePolicy.requiredBond(corridorId, obligation.destinationReserve);
|
uint256 requiredBond = feePolicy.requiredBond(corridorId, obligation.destinationReserve);
|
||||||
fulfillerRegistry.lockBond(obligationId, msg.sender, corridorId, requiredBond);
|
fulfillerRegistry.lockBond(obligationId, fulfiller, corridorId, requiredBond);
|
||||||
liquidityVault.fulfillReservedLiquidity(obligationId, intent.recipient);
|
if (releaseLocalLiquidity) {
|
||||||
|
liquidityVault.fulfillReservedLiquidity(obligationId, intent.recipient);
|
||||||
|
}
|
||||||
|
|
||||||
_commitments[obligationId] = AtomicTypes.AtomicCommitment({
|
_commitments[obligationId] = AtomicTypes.AtomicCommitment({
|
||||||
intentId: intent.intentId,
|
intentId: intent.intentId,
|
||||||
fulfiller: msg.sender,
|
fulfiller: fulfiller,
|
||||||
reservedLiquidity: obligation.destinationReserve,
|
reservedLiquidity: obligation.destinationReserve,
|
||||||
bondAmount: requiredBond,
|
bondAmount: requiredBond,
|
||||||
expiry: block.timestamp + _corridors[corridorId].settlementTimeout,
|
expiry: block.timestamp + _corridors[corridorId].settlementTimeout,
|
||||||
settlementMode: mode
|
settlementMode: mode
|
||||||
});
|
});
|
||||||
obligation.fulfiller = msg.sender;
|
obligation.fulfiller = fulfiller;
|
||||||
obligation.status = AtomicTypes.ObligationStatus.Fulfilled;
|
obligation.status = AtomicTypes.ObligationStatus.Fulfilled;
|
||||||
|
|
||||||
emit CommitmentAccepted(obligationId, msg.sender, requiredBond);
|
emit CommitmentAccepted(obligationId, fulfiller, requiredBond);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initiateSettlement(bytes32 obligationId, bytes calldata settlementData)
|
function initiateSettlement(bytes32 obligationId, bytes calldata settlementData)
|
||||||
@@ -276,7 +322,9 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
if (replenishAmount < obligation.destinationReserve) revert MinimumReplenishNotMet();
|
if (replenishAmount < obligation.destinationReserve) revert MinimumReplenishNotMet();
|
||||||
|
|
||||||
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
|
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
|
||||||
liquidityVault.reconcileSettlement(corridorId, intent.assetOut, replenishAmount, msg.sender);
|
if (!_asyncDestinationCorridors[corridorId]) {
|
||||||
|
liquidityVault.reconcileSettlement(corridorId, intent.assetOut, replenishAmount, msg.sender);
|
||||||
|
}
|
||||||
fulfillerRegistry.releaseBond(obligationId);
|
fulfillerRegistry.releaseBond(obligationId);
|
||||||
obligation.status = AtomicTypes.ObligationStatus.Settled;
|
obligation.status = AtomicTypes.ObligationStatus.Settled;
|
||||||
emit SettlementConfirmed(obligationId, replenishAmount);
|
emit SettlementConfirmed(obligationId, replenishAmount);
|
||||||
@@ -294,7 +342,9 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
if (refundTo == address(0)) {
|
if (refundTo == address(0)) {
|
||||||
refundTo = payer;
|
refundTo = payer;
|
||||||
}
|
}
|
||||||
liquidityVault.releaseReservation(obligationId);
|
if (!_asyncDestinationCorridors[corridorId]) {
|
||||||
|
liquidityVault.releaseReservation(obligationId);
|
||||||
|
}
|
||||||
uint256 refunded = obligationEscrow.refundRemaining(obligationId, refundTo);
|
uint256 refunded = obligationEscrow.refundRemaining(obligationId, refundTo);
|
||||||
_corridors[corridorId].degraded = true;
|
_corridors[corridorId].degraded = true;
|
||||||
obligation.status = AtomicTypes.ObligationStatus.Refunded;
|
obligation.status = AtomicTypes.ObligationStatus.Refunded;
|
||||||
@@ -335,4 +385,8 @@ contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridg
|
|||||||
function getRefundRecipient(bytes32 obligationId) external view returns (address) {
|
function getRefundRecipient(bytes32 obligationId) external view returns (address) {
|
||||||
return _refundRecipients[obligationId];
|
return _refundRecipients[obligationId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAsyncDestinationCorridor(bytes32 corridorId) external view returns (bool) {
|
||||||
|
return _asyncDestinationCorridors[corridorId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
contracts/x402/AtomicCwUsdcUsdcPurchase.sol
Normal file
97
contracts/x402/AtomicCwUsdcUsdcPurchase.sol
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
|
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
|
||||||
|
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||||
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
|
||||||
|
interface IERC3009TransferWithAuthorization {
|
||||||
|
function transferWithAuthorization(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 value,
|
||||||
|
uint256 validAfter,
|
||||||
|
uint256 validBefore,
|
||||||
|
bytes32 nonce,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) external;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title AtomicCwUsdcUsdcPurchase
|
||||||
|
* @notice Executes a Polygon x402 cWUSDC_V2 payment and native USDC fulfillment in one transaction.
|
||||||
|
* @dev The buyer signs ERC-3009 for cWUSDC_V2 to `treasury`. This contract relays that authorization
|
||||||
|
* and transfers matching USDC inventory to the buyer. If either leg fails, the whole transaction reverts.
|
||||||
|
*/
|
||||||
|
contract AtomicCwUsdcUsdcPurchase is Ownable, Pausable, ReentrancyGuard {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
IERC3009TransferWithAuthorization public immutable cWUSDC;
|
||||||
|
IERC20 public immutable usdc;
|
||||||
|
address public treasury;
|
||||||
|
|
||||||
|
event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury);
|
||||||
|
event AtomicPurchase(
|
||||||
|
address indexed payer,
|
||||||
|
address indexed recipient,
|
||||||
|
address indexed treasury,
|
||||||
|
uint256 amount,
|
||||||
|
bytes32 authorizationNonce
|
||||||
|
);
|
||||||
|
|
||||||
|
error ZeroAddress();
|
||||||
|
error ZeroAmount();
|
||||||
|
|
||||||
|
constructor(address cWUSDC_, address usdc_, address treasury_, address owner_) Ownable(owner_) {
|
||||||
|
if (cWUSDC_ == address(0) || usdc_ == address(0) || treasury_ == address(0) || owner_ == address(0)) {
|
||||||
|
revert ZeroAddress();
|
||||||
|
}
|
||||||
|
cWUSDC = IERC3009TransferWithAuthorization(cWUSDC_);
|
||||||
|
usdc = IERC20(usdc_);
|
||||||
|
treasury = treasury_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTreasury(address newTreasury) external onlyOwner {
|
||||||
|
if (newTreasury == address(0)) revert ZeroAddress();
|
||||||
|
emit TreasuryUpdated(treasury, newTreasury);
|
||||||
|
treasury = newTreasury;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause() external onlyOwner {
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpause() external onlyOwner {
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function purchaseWithAuthorization(
|
||||||
|
address payer,
|
||||||
|
address recipient,
|
||||||
|
uint256 amount,
|
||||||
|
uint256 validAfter,
|
||||||
|
uint256 validBefore,
|
||||||
|
bytes32 nonce,
|
||||||
|
uint8 v,
|
||||||
|
bytes32 r,
|
||||||
|
bytes32 s
|
||||||
|
) external nonReentrant whenNotPaused {
|
||||||
|
if (payer == address(0) || recipient == address(0)) revert ZeroAddress();
|
||||||
|
if (amount == 0) revert ZeroAmount();
|
||||||
|
|
||||||
|
address currentTreasury = treasury;
|
||||||
|
cWUSDC.transferWithAuthorization(payer, currentTreasury, amount, validAfter, validBefore, nonce, v, r, s);
|
||||||
|
usdc.safeTransfer(recipient, amount);
|
||||||
|
|
||||||
|
emit AtomicPurchase(payer, recipient, currentTreasury, amount, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withdrawToken(address token, address to, uint256 amount) external onlyOwner {
|
||||||
|
if (token == address(0) || to == address(0)) revert ZeroAddress();
|
||||||
|
IERC20(token).safeTransfer(to, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
deploy/nginx/omnl-portal-auth-5820.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5820.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5820 portal upstream auth (key from CT 5826).
|
||||||
|
proxy_set_header Authorization "Bearer 0bb767cb086867ad9ea76b49ba4698667cdc280d8090c8747f0659464b9b1243";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5821.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5821.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5821 portal upstream auth (key from CT 5827).
|
||||||
|
proxy_set_header Authorization "Bearer 90eb70ddfae177dd378dadcb52038fa938358ca31dadeca24ec275e5b0f5e7a1";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5822.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5822.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5822 portal upstream auth (key from CT 5827).
|
||||||
|
proxy_set_header Authorization "Bearer 90eb70ddfae177dd378dadcb52038fa938358ca31dadeca24ec275e5b0f5e7a1";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5823.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5823.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5823 portal upstream auth (key from CT 5828).
|
||||||
|
proxy_set_header Authorization "Bearer 79db6703aabb0fdfaa2d508cc002701b0268a4d349c95a782beeba437834e6d8";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5824.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5824.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5824 portal upstream auth (key from CT 5825).
|
||||||
|
proxy_set_header Authorization "Bearer c859d5184f9d8dd78f15af3014b52f1e2191442e2fcc6cd5fa4b6148b064e5b8";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5825.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5825.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5825 portal upstream auth.
|
||||||
|
proxy_set_header Authorization "Bearer c859d5184f9d8dd78f15af3014b52f1e2191442e2fcc6cd5fa4b6148b064e5b8";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5826.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5826.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5826 portal upstream auth.
|
||||||
|
proxy_set_header Authorization "Bearer 0bb767cb086867ad9ea76b49ba4698667cdc280d8090c8747f0659464b9b1243";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5827.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5827.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5827 portal upstream auth.
|
||||||
|
proxy_set_header Authorization "Bearer 90eb70ddfae177dd378dadcb52038fa938358ca31dadeca24ec275e5b0f5e7a1";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
3
deploy/nginx/omnl-portal-auth-5828.conf.inc
Normal file
3
deploy/nginx/omnl-portal-auth-5828.conf.inc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated — do not commit. CT 5828 portal upstream auth.
|
||||||
|
proxy_set_header Authorization "Bearer 79db6703aabb0fdfaa2d508cc002701b0268a4d349c95a782beeba437834e6d8";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
# Placeholder — replaced per-CTID by push-portal-env-to-lxc.sh / generate-portal-nginx-auth-snippet.sh
|
# Auto-generated — do not commit. CT 5827 portal upstream auth.
|
||||||
# Empty headers until super-admin key is injected.
|
proxy_set_header Authorization "Bearer 90eb70ddfae177dd378dadcb52038fa938358ca31dadeca24ec275e5b0f5e7a1";
|
||||||
|
proxy_set_header X-OMNL-Portal-Auth "8944affdb51fb9d8dc47c59df345506d92fbdcb892c4b0ed6710ac1918de5866";
|
||||||
|
|||||||
@@ -2,13 +2,19 @@
|
|||||||
|
|
||||||
**Date**: 2025-01-12
|
**Date**: 2025-01-12
|
||||||
**Status**: Implementation Guide
|
**Status**: Implementation Guide
|
||||||
**Purpose**: 1:1 backing mechanism for CompliantUSDT and CompliantUSDC with official tokens
|
**Purpose**: Legacy mainnet reserve-vault integration notes for official USDT/USDC custody and PMM mirror liquidity.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Canonical taxonomy note
|
||||||
|
|
||||||
|
Chain 138 `c*` assets such as cUSDT and cUSDC are **GRU M1 eMoney / tokenized fiat instruments**, not Tether USDT or Circle USDC issuances and not generic stablecoins. Their primary backing model is the GRU reserve and attestation framework documented in `docs/04-configuration/CHAIN138_RWA_AND_TRANSPORT_ARCHITECTURE.md` and `docs/04-configuration/GRU_HUB_TOKEN_NAMING.md`.
|
||||||
|
|
||||||
|
This document covers the legacy `StablecoinReserveVault` integration surface for custody of **official third-party USDT/USDC** and mirror-liquidity workflows. Treat official USDT/USDC as quote-side / redemption assets, not as the legal identity of Chain 138 `c*` eMoney.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The Reserve Backing Mechanism enables CompliantUSDT (cUSDT) and CompliantUSDC (cUSDC) to maintain 1:1 backing with official Tether USDT and Circle USDC tokens. This provides transparency, trust, and exchangeability with official stablecoins.
|
The reserve-vault surface can custody official Tether USDT and Circle USDC tokens and expose operational checks for routes that exchange Chain 138 eMoney against those public issuer assets. It is not the canonical source of truth for GRU M1 issuance backing.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,12 +25,12 @@ The Reserve Backing Mechanism enables CompliantUSDT (cUSDT) and CompliantUSDC (c
|
|||||||
**Location**: `contracts/reserve/StablecoinReserveVault.sol`
|
**Location**: `contracts/reserve/StablecoinReserveVault.sol`
|
||||||
|
|
||||||
**Purpose**:
|
**Purpose**:
|
||||||
- Lock official USDT/USDC tokens
|
- Lock official third-party USDT/USDC tokens for designated mirror-liquidity or redemption workflows
|
||||||
- Mint cUSDT/cUSDC tokens 1:1 against locked reserves
|
- Track vault balances and operational reserves for official-token custody
|
||||||
- Enable redemption of cUSDT/cUSDC for official tokens
|
- Support controlled exchange workflows between Chain 138 eMoney and public issuer assets where policy permits
|
||||||
|
|
||||||
**Key Features**:
|
**Key Features**:
|
||||||
- 1:1 minting ratio (1 official token = 1 compliant token)
|
- Operational face-value accounting for official-token custody workflows
|
||||||
- Reserve tracking and verification
|
- Reserve tracking and verification
|
||||||
- Pause mechanism for emergencies
|
- Pause mechanism for emergencies
|
||||||
- Access control with role-based permissions
|
- Access control with role-based permissions
|
||||||
@@ -45,7 +51,7 @@ The Reserve Backing Mechanism enables CompliantUSDT (cUSDT) and CompliantUSDC (c
|
|||||||
|
|
||||||
3. **Network Requirements**:
|
3. **Network Requirements**:
|
||||||
- Vault should be deployed on Ethereum Mainnet (or network with official tokens)
|
- Vault should be deployed on Ethereum Mainnet (or network with official tokens)
|
||||||
- Compliant tokens can be on Chain 138 (connected via bridge)
|
- Chain 138 cUSDT/cUSDC remain GRU M1 eMoney instruments; bridge and mirror workflows must preserve that taxonomy
|
||||||
|
|
||||||
### Deployment Steps
|
### Deployment Steps
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ remappings = [
|
|||||||
src = "contracts/tokens,contracts/interfaces"
|
src = "contracts/tokens,contracts/interfaces"
|
||||||
out = "out/scopes/tokens"
|
out = "out/scopes/tokens"
|
||||||
cache_path = "cache/scopes/tokens"
|
cache_path = "cache/scopes/tokens"
|
||||||
|
skip = ["contracts/tokens/*V2.sol"]
|
||||||
solc = "0.8.20"
|
solc = "0.8.20"
|
||||||
optimizer = true
|
optimizer = true
|
||||||
optimizer_runs = 200
|
optimizer_runs = 200
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Token icon URLs for display in the DApp (symbol or key → URL).
|
* Token icon URLs for display in the DApp (symbol or key → URL).
|
||||||
* Aligned with token-lists logoURI (IPFS-hosted via Pinata).
|
* Aligned with token-lists logoURI (IPFS-hosted via Pinata).
|
||||||
* Includes Chain 138, Cronos (25) ISO-4217 W tokens, and compliant stables.
|
* Includes Chain 138, Cronos (25) ISO-4217 W tokens, and compliant eMoney.
|
||||||
*/
|
*/
|
||||||
const IPFS = 'https://ipfs.io/ipfs';
|
const IPFS = 'https://ipfs.io/ipfs';
|
||||||
const ETH_LOGO = `${IPFS}/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong`;
|
const ETH_LOGO = `${IPFS}/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong`;
|
||||||
|
|||||||
1
lib/weth10
Submodule
1
lib/weth10
Submodule
Submodule lib/weth10 added at 34d2712876
@@ -17,3 +17,4 @@ export * from './hybx/HttpHybxClient';
|
|||||||
export * from './reconciliation/ReconciliationStatus';
|
export * from './reconciliation/ReconciliationStatus';
|
||||||
export * from './resilience/circuitBreaker';
|
export * from './resilience/circuitBreaker';
|
||||||
export * from './omnl/omnl-api-auth';
|
export * from './omnl/omnl-api-auth';
|
||||||
|
export * from './payments/CronosFacilitatorAdapter';
|
||||||
|
|||||||
@@ -33,3 +33,4 @@ __exportStar(require("./hybx/HttpHybxClient"), exports);
|
|||||||
__exportStar(require("./reconciliation/ReconciliationStatus"), exports);
|
__exportStar(require("./reconciliation/ReconciliationStatus"), exports);
|
||||||
__exportStar(require("./resilience/circuitBreaker"), exports);
|
__exportStar(require("./resilience/circuitBreaker"), exports);
|
||||||
__exportStar(require("./omnl/omnl-api-auth"), exports);
|
__exportStar(require("./omnl/omnl-api-auth"), exports);
|
||||||
|
__exportStar(require("./payments/CronosFacilitatorAdapter"), exports);
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ const circuitBreaker_1 = require("./resilience/circuitBreaker");
|
|||||||
requestTimeoutMs: 1000,
|
requestTimeoutMs: 1000,
|
||||||
maxRetries: 1,
|
maxRetries: 1,
|
||||||
},
|
},
|
||||||
}), /refuses production/);
|
}), /production requires HYBX_UNIFIED_API_OK=1/);
|
||||||
});
|
});
|
||||||
(0, node_test_1.it)('parses webhook payload shape', () => {
|
(0, node_test_1.it)('parses webhook payload shape', () => {
|
||||||
const client = new HttpHybxClient_1.HttpHybxClient({
|
const client = new HttpHybxClient_1.HttpHybxClient({
|
||||||
|
|||||||
153
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.d.ts
vendored
Normal file
153
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.d.ts
vendored
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
export type ExactPaymentScheme = 'exact';
|
||||||
|
export interface PaymentMoney {
|
||||||
|
asset: string;
|
||||||
|
amountBaseUnits: string;
|
||||||
|
decimals: number;
|
||||||
|
}
|
||||||
|
export interface PaymentIntent {
|
||||||
|
id: string;
|
||||||
|
resource: string;
|
||||||
|
description: string;
|
||||||
|
network: string;
|
||||||
|
payTo: string;
|
||||||
|
price: PaymentMoney;
|
||||||
|
maxTimeoutSeconds: number;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
export interface PaymentChallenge {
|
||||||
|
x402Version: number;
|
||||||
|
scheme: ExactPaymentScheme;
|
||||||
|
network: string;
|
||||||
|
paymentRequirements: FacilitatorPaymentRequirements;
|
||||||
|
}
|
||||||
|
export interface SignedPayment {
|
||||||
|
paymentHeader: string;
|
||||||
|
paymentRequirements: FacilitatorPaymentRequirements;
|
||||||
|
}
|
||||||
|
export interface VerifyResult {
|
||||||
|
ok: boolean;
|
||||||
|
reason?: string;
|
||||||
|
normalized?: {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
asset?: string;
|
||||||
|
amountBaseUnits?: string;
|
||||||
|
network?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface SettlementResult {
|
||||||
|
ok: boolean;
|
||||||
|
txHash?: string;
|
||||||
|
network: string;
|
||||||
|
timestamp?: string;
|
||||||
|
error?: string;
|
||||||
|
raw?: unknown;
|
||||||
|
}
|
||||||
|
export interface PaymentRailCapabilities {
|
||||||
|
schemes: string[];
|
||||||
|
assets: string[];
|
||||||
|
supportsSettlement: boolean;
|
||||||
|
}
|
||||||
|
export interface PaymentRailAdapter {
|
||||||
|
readonly id: string;
|
||||||
|
readonly network: string;
|
||||||
|
getCapabilities(): Promise<PaymentRailCapabilities>;
|
||||||
|
createChallenge(intent: PaymentIntent): Promise<PaymentChallenge>;
|
||||||
|
signPayment(input: SignPaymentInput): Promise<SignedPayment>;
|
||||||
|
verifyPayment(input: SignedPayment): Promise<VerifyResult>;
|
||||||
|
settlePayment(input: SignedPayment): Promise<SettlementResult>;
|
||||||
|
}
|
||||||
|
export interface SignPaymentInput {
|
||||||
|
intent: PaymentIntent;
|
||||||
|
signer: unknown;
|
||||||
|
validAfter?: number;
|
||||||
|
validBefore?: number;
|
||||||
|
}
|
||||||
|
export interface FacilitatorKind {
|
||||||
|
x402Version: number;
|
||||||
|
scheme: string;
|
||||||
|
network: string;
|
||||||
|
}
|
||||||
|
export interface FacilitatorSupportedResponse {
|
||||||
|
kinds: FacilitatorKind[];
|
||||||
|
}
|
||||||
|
export interface FacilitatorVerifyResponse {
|
||||||
|
isValid: boolean;
|
||||||
|
invalidReason: string | null;
|
||||||
|
}
|
||||||
|
export interface FacilitatorSettleResponse {
|
||||||
|
x402Version: number;
|
||||||
|
event: string;
|
||||||
|
txHash?: string;
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
value?: string;
|
||||||
|
network: string;
|
||||||
|
timestamp: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
export interface FacilitatorPaymentRequirements {
|
||||||
|
scheme: ExactPaymentScheme;
|
||||||
|
network: string;
|
||||||
|
payTo: string;
|
||||||
|
asset: string;
|
||||||
|
description: string;
|
||||||
|
mimeType: string;
|
||||||
|
maxAmountRequired: string;
|
||||||
|
maxTimeoutSeconds: number;
|
||||||
|
resource?: string;
|
||||||
|
extra?: Record<string, unknown>;
|
||||||
|
outputSchema?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
export interface FacilitatorVerifyRequest {
|
||||||
|
x402Version: number;
|
||||||
|
paymentHeader: string;
|
||||||
|
paymentRequirements: FacilitatorPaymentRequirements;
|
||||||
|
}
|
||||||
|
export interface FacilitatorClientLike {
|
||||||
|
getSupported(): Promise<FacilitatorSupportedResponse>;
|
||||||
|
generatePaymentRequirements(input: {
|
||||||
|
payTo: string;
|
||||||
|
asset?: string;
|
||||||
|
description?: string;
|
||||||
|
maxAmountRequired?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
maxTimeoutSeconds?: number;
|
||||||
|
resource?: string;
|
||||||
|
extra?: Record<string, unknown>;
|
||||||
|
outputSchema?: Record<string, unknown>;
|
||||||
|
}): FacilitatorPaymentRequirements;
|
||||||
|
generatePaymentHeader(input: {
|
||||||
|
to: string;
|
||||||
|
value: string;
|
||||||
|
asset?: string;
|
||||||
|
signer: unknown;
|
||||||
|
validAfter?: number;
|
||||||
|
validBefore?: number;
|
||||||
|
}): Promise<string>;
|
||||||
|
verifyPayment(request: FacilitatorVerifyRequest): Promise<FacilitatorVerifyResponse>;
|
||||||
|
settlePayment(request: FacilitatorVerifyRequest): Promise<FacilitatorSettleResponse>;
|
||||||
|
}
|
||||||
|
export interface CronosFacilitatorAdapterOptions {
|
||||||
|
facilitator: FacilitatorClientLike;
|
||||||
|
network: string;
|
||||||
|
asset?: string;
|
||||||
|
defaultDecimals?: number;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
export declare class CronosFacilitatorAdapter implements PaymentRailAdapter {
|
||||||
|
readonly id: string;
|
||||||
|
readonly network: string;
|
||||||
|
private readonly facilitator;
|
||||||
|
private readonly asset?;
|
||||||
|
private readonly defaultDecimals;
|
||||||
|
constructor(options: CronosFacilitatorAdapterOptions);
|
||||||
|
getCapabilities(): Promise<PaymentRailCapabilities>;
|
||||||
|
createChallenge(intent: PaymentIntent): Promise<PaymentChallenge>;
|
||||||
|
signPayment(input: SignPaymentInput): Promise<SignedPayment>;
|
||||||
|
verifyPayment(input: SignedPayment): Promise<VerifyResult>;
|
||||||
|
settlePayment(input: SignedPayment): Promise<SettlementResult>;
|
||||||
|
private toVerifyRequest;
|
||||||
|
private resolveAsset;
|
||||||
|
private assertIntent;
|
||||||
|
}
|
||||||
117
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.js
vendored
Normal file
117
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.js
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.CronosFacilitatorAdapter = void 0;
|
||||||
|
const DEFAULT_SCHEME = 'exact';
|
||||||
|
const DEFAULT_DECIMALS = 6;
|
||||||
|
const SETTLED_EVENT = 'payment.settled';
|
||||||
|
function assertBaseUnitAmount(amountBaseUnits) {
|
||||||
|
if (!/^[0-9]+$/.test(amountBaseUnits)) {
|
||||||
|
throw new Error(`PaymentIntent.price.amountBaseUnits must be an unsigned base-unit integer, got: ${amountBaseUnits}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertNetworkMatch(expectedNetwork, actualNetwork) {
|
||||||
|
if (expectedNetwork !== actualNetwork) {
|
||||||
|
throw new Error(`PaymentIntent network mismatch: expected ${expectedNetwork}, got ${actualNetwork}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class CronosFacilitatorAdapter {
|
||||||
|
id;
|
||||||
|
network;
|
||||||
|
facilitator;
|
||||||
|
asset;
|
||||||
|
defaultDecimals;
|
||||||
|
constructor(options) {
|
||||||
|
this.id = options.id ?? 'cronos-facilitator';
|
||||||
|
this.network = options.network;
|
||||||
|
this.facilitator = options.facilitator;
|
||||||
|
this.asset = options.asset;
|
||||||
|
this.defaultDecimals = options.defaultDecimals ?? DEFAULT_DECIMALS;
|
||||||
|
}
|
||||||
|
async getCapabilities() {
|
||||||
|
const supported = await this.facilitator.getSupported();
|
||||||
|
const schemes = [...new Set(supported.kinds.map((kind) => kind.scheme))];
|
||||||
|
const assets = this.asset ? [this.asset] : [];
|
||||||
|
return {
|
||||||
|
schemes,
|
||||||
|
assets,
|
||||||
|
supportsSettlement: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async createChallenge(intent) {
|
||||||
|
this.assertIntent(intent);
|
||||||
|
const paymentRequirements = this.facilitator.generatePaymentRequirements({
|
||||||
|
payTo: intent.payTo,
|
||||||
|
asset: this.resolveAsset(intent),
|
||||||
|
description: intent.description,
|
||||||
|
maxAmountRequired: intent.price.amountBaseUnits,
|
||||||
|
maxTimeoutSeconds: intent.maxTimeoutSeconds,
|
||||||
|
resource: intent.resource,
|
||||||
|
extra: {
|
||||||
|
intentId: intent.id,
|
||||||
|
decimals: intent.price.decimals || this.defaultDecimals,
|
||||||
|
...intent.metadata,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
x402Version: 1,
|
||||||
|
scheme: DEFAULT_SCHEME,
|
||||||
|
network: this.network,
|
||||||
|
paymentRequirements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async signPayment(input) {
|
||||||
|
this.assertIntent(input.intent);
|
||||||
|
const challenge = await this.createChallenge(input.intent);
|
||||||
|
const paymentHeader = await this.facilitator.generatePaymentHeader({
|
||||||
|
to: input.intent.payTo,
|
||||||
|
value: input.intent.price.amountBaseUnits,
|
||||||
|
asset: this.resolveAsset(input.intent),
|
||||||
|
signer: input.signer,
|
||||||
|
validAfter: input.validAfter,
|
||||||
|
validBefore: input.validBefore,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
paymentHeader,
|
||||||
|
paymentRequirements: challenge.paymentRequirements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async verifyPayment(input) {
|
||||||
|
const response = await this.facilitator.verifyPayment(this.toVerifyRequest(input));
|
||||||
|
return {
|
||||||
|
ok: response.isValid,
|
||||||
|
reason: response.invalidReason ?? undefined,
|
||||||
|
normalized: {
|
||||||
|
to: input.paymentRequirements.payTo,
|
||||||
|
asset: input.paymentRequirements.asset,
|
||||||
|
amountBaseUnits: input.paymentRequirements.maxAmountRequired,
|
||||||
|
network: input.paymentRequirements.network,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async settlePayment(input) {
|
||||||
|
const response = await this.facilitator.settlePayment(this.toVerifyRequest(input));
|
||||||
|
return {
|
||||||
|
ok: response.event === SETTLED_EVENT,
|
||||||
|
txHash: response.txHash,
|
||||||
|
network: response.network,
|
||||||
|
timestamp: response.timestamp,
|
||||||
|
error: response.error,
|
||||||
|
raw: response,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
toVerifyRequest(input) {
|
||||||
|
return {
|
||||||
|
x402Version: 1,
|
||||||
|
paymentHeader: input.paymentHeader,
|
||||||
|
paymentRequirements: input.paymentRequirements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
resolveAsset(intent) {
|
||||||
|
return this.asset ?? intent.price.asset;
|
||||||
|
}
|
||||||
|
assertIntent(intent) {
|
||||||
|
assertNetworkMatch(this.network, intent.network);
|
||||||
|
assertBaseUnitAmount(intent.price.amountBaseUnits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CronosFacilitatorAdapter = CronosFacilitatorAdapter;
|
||||||
1
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.test.d.ts
vendored
Normal file
1
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.test.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
233
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.test.js
vendored
Normal file
233
packages/integration-foundation/dist/payments/CronosFacilitatorAdapter.test.js
vendored
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const node_test_1 = require("node:test");
|
||||||
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
||||||
|
const CronosFacilitatorAdapter_1 = require("./CronosFacilitatorAdapter");
|
||||||
|
class StubFacilitatorClient {
|
||||||
|
lastRequirementsInput;
|
||||||
|
lastHeaderInput;
|
||||||
|
lastVerifyRequest;
|
||||||
|
lastSettleRequest;
|
||||||
|
verifyResponse = { isValid: true, invalidReason: null };
|
||||||
|
settleResponse = {
|
||||||
|
x402Version: 1,
|
||||||
|
event: 'payment.settled',
|
||||||
|
txHash: '0xsettled',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
timestamp: '2026-07-10T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
async getSupported() {
|
||||||
|
return {
|
||||||
|
kinds: [
|
||||||
|
{ x402Version: 1, scheme: 'exact', network: 'cronos-testnet' },
|
||||||
|
{ x402Version: 1, scheme: 'exact', network: 'cronos-mainnet' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
generatePaymentRequirements(input) {
|
||||||
|
this.lastRequirementsInput = input;
|
||||||
|
return {
|
||||||
|
scheme: 'exact',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
payTo: input.payTo,
|
||||||
|
asset: input.asset ?? '0xasset',
|
||||||
|
description: input.description ?? 'default',
|
||||||
|
mimeType: input.mimeType ?? 'application/json',
|
||||||
|
maxAmountRequired: input.maxAmountRequired ?? '0',
|
||||||
|
maxTimeoutSeconds: input.maxTimeoutSeconds ?? 300,
|
||||||
|
resource: input.resource,
|
||||||
|
extra: input.extra,
|
||||||
|
outputSchema: input.outputSchema,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async generatePaymentHeader(input) {
|
||||||
|
this.lastHeaderInput = input;
|
||||||
|
return 'base64-header';
|
||||||
|
}
|
||||||
|
async verifyPayment(request) {
|
||||||
|
this.lastVerifyRequest = request;
|
||||||
|
return this.verifyResponse;
|
||||||
|
}
|
||||||
|
async settlePayment(request) {
|
||||||
|
this.lastSettleRequest = request;
|
||||||
|
return this.settleResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function buildIntent(overrides = {}) {
|
||||||
|
return {
|
||||||
|
id: 'intent-1',
|
||||||
|
resource: '/api/paid',
|
||||||
|
description: 'Premium route access',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
payTo: '0xpayee',
|
||||||
|
price: {
|
||||||
|
asset: '0xasset-from-intent',
|
||||||
|
amountBaseUnits: '1000000',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
maxTimeoutSeconds: 300,
|
||||||
|
metadata: {
|
||||||
|
tenantId: 'tenant-a',
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(0, node_test_1.describe)('CronosFacilitatorAdapter', () => {
|
||||||
|
(0, node_test_1.it)('returns deduplicated capabilities and configured asset', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
});
|
||||||
|
const capabilities = await adapter.getCapabilities();
|
||||||
|
strict_1.default.deepEqual(capabilities, {
|
||||||
|
schemes: ['exact'],
|
||||||
|
assets: ['0xconfigured-asset'],
|
||||||
|
supportsSettlement: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('creates a challenge with normalized base-unit amount and metadata', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
});
|
||||||
|
const challenge = await adapter.createChallenge(buildIntent());
|
||||||
|
strict_1.default.equal(challenge.scheme, 'exact');
|
||||||
|
strict_1.default.equal(challenge.network, 'cronos-testnet');
|
||||||
|
strict_1.default.equal(challenge.paymentRequirements.maxAmountRequired, '1000000');
|
||||||
|
strict_1.default.equal(challenge.paymentRequirements.asset, '0xconfigured-asset');
|
||||||
|
strict_1.default.deepEqual(facilitator.lastRequirementsInput, {
|
||||||
|
payTo: '0xpayee',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
description: 'Premium route access',
|
||||||
|
maxAmountRequired: '1000000',
|
||||||
|
maxTimeoutSeconds: 300,
|
||||||
|
resource: '/api/paid',
|
||||||
|
extra: {
|
||||||
|
intentId: 'intent-1',
|
||||||
|
decimals: 6,
|
||||||
|
tenantId: 'tenant-a',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('signs through the facilitator header generator with exact base units', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
});
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
validAfter: 10,
|
||||||
|
validBefore: 20,
|
||||||
|
});
|
||||||
|
strict_1.default.equal(signed.paymentHeader, 'base64-header');
|
||||||
|
strict_1.default.deepEqual(facilitator.lastHeaderInput, {
|
||||||
|
to: '0xpayee',
|
||||||
|
value: '1000000',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
validAfter: 10,
|
||||||
|
validBefore: 20,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('verifies and returns normalized payment context', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
facilitator.verifyResponse = {
|
||||||
|
isValid: false,
|
||||||
|
invalidReason: 'authorization expired',
|
||||||
|
};
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
});
|
||||||
|
const verified = await adapter.verifyPayment(signed);
|
||||||
|
strict_1.default.equal(verified.ok, false);
|
||||||
|
strict_1.default.equal(verified.reason, 'authorization expired');
|
||||||
|
strict_1.default.deepEqual(verified.normalized, {
|
||||||
|
to: '0xpayee',
|
||||||
|
asset: '0xasset-from-intent',
|
||||||
|
amountBaseUnits: '1000000',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
strict_1.default.equal(facilitator.lastVerifyRequest.x402Version, 1);
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('maps settlement success into a rail-level result', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
facilitator.settleResponse = {
|
||||||
|
x402Version: 1,
|
||||||
|
event: 'payment.settled',
|
||||||
|
txHash: '0xabc',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
timestamp: '2026-07-10T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
});
|
||||||
|
const settled = await adapter.settlePayment(signed);
|
||||||
|
strict_1.default.equal(settled.ok, true);
|
||||||
|
strict_1.default.equal(settled.txHash, '0xabc');
|
||||||
|
strict_1.default.equal(settled.network, 'cronos-testnet');
|
||||||
|
strict_1.default.equal(facilitator.lastSettleRequest.x402Version, 1);
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('maps non-settled facilitator events into failures', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
facilitator.settleResponse = {
|
||||||
|
x402Version: 1,
|
||||||
|
event: 'payment.failed',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
timestamp: '2026-07-10T00:00:00.000Z',
|
||||||
|
error: 'insufficient allowance',
|
||||||
|
};
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
});
|
||||||
|
const settled = await adapter.settlePayment(signed);
|
||||||
|
strict_1.default.equal(settled.ok, false);
|
||||||
|
strict_1.default.equal(settled.error, 'insufficient allowance');
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('rejects non-base-unit amounts before calling the facilitator', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
await strict_1.default.rejects(() => adapter.createChallenge(buildIntent({
|
||||||
|
price: {
|
||||||
|
asset: '0xasset',
|
||||||
|
amountBaseUnits: '1.5',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
})), /amountBaseUnits must be an unsigned base-unit integer/);
|
||||||
|
});
|
||||||
|
(0, node_test_1.it)('rejects network mismatches before signing or settlement', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter_1.CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-mainnet',
|
||||||
|
});
|
||||||
|
await strict_1.default.rejects(() => adapter.signPayment({ intent: buildIntent(), signer: { id: 'signer-1' } }), /network mismatch/);
|
||||||
|
});
|
||||||
|
});
|
||||||
90
packages/integration-foundation/package-lock.json
generated
Normal file
90
packages/integration-foundation/package-lock.json
generated
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"name": "@dbis/integration-foundation",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@dbis/integration-foundation",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"typescript": "^5.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"../../../node_modules/.pnpm/@types+node@20.19.33/node_modules/@types/node": {
|
||||||
|
"version": "20.19.33",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dprint/formatter": "^0.4.1",
|
||||||
|
"@dprint/typescript": "0.93.4",
|
||||||
|
"@esfx/canceltoken": "^1.0.0",
|
||||||
|
"@eslint/js": "^9.20.0",
|
||||||
|
"@octokit/rest": "^21.1.1",
|
||||||
|
"@types/chai": "^4.3.20",
|
||||||
|
"@types/diff": "^7.0.1",
|
||||||
|
"@types/minimist": "^1.2.5",
|
||||||
|
"@types/mocha": "^10.0.10",
|
||||||
|
"@types/ms": "^0.7.34",
|
||||||
|
"@types/node": "latest",
|
||||||
|
"@types/source-map-support": "^0.5.10",
|
||||||
|
"@types/which": "^3.0.4",
|
||||||
|
"@typescript-eslint/rule-tester": "^8.24.1",
|
||||||
|
"@typescript-eslint/type-utils": "^8.24.1",
|
||||||
|
"@typescript-eslint/utils": "^8.24.1",
|
||||||
|
"azure-devops-node-api": "^14.1.0",
|
||||||
|
"c8": "^10.1.3",
|
||||||
|
"chai": "^4.5.0",
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
|
"diff": "^7.0.0",
|
||||||
|
"dprint": "^0.49.0",
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"eslint": "^9.20.1",
|
||||||
|
"eslint-formatter-autolinkable-stylish": "^1.4.0",
|
||||||
|
"eslint-plugin-regexp": "^2.7.0",
|
||||||
|
"fast-xml-parser": "^4.5.2",
|
||||||
|
"glob": "^10.4.5",
|
||||||
|
"globals": "^15.15.0",
|
||||||
|
"hereby": "^1.10.0",
|
||||||
|
"jsonc-parser": "^3.3.1",
|
||||||
|
"knip": "^5.44.4",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"mocha": "^10.8.2",
|
||||||
|
"mocha-fivemat-progress-reporter": "^0.1.0",
|
||||||
|
"monocart-coverage-reports": "^2.12.1",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"playwright": "^1.50.1",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.24.1",
|
||||||
|
"which": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"resolved": "../../../node_modules/.pnpm/@types+node@20.19.33/node_modules/@types/node",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"resolved": "../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript",
|
||||||
|
"link": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"files": ["dist"],
|
"files": ["dist"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "node --test dist/integration-foundation.test.js dist/hybx/openapi-placeholder-contract.test.js"
|
"test": "node --test dist/integration-foundation.test.js dist/payments/CronosFacilitatorAdapter.test.js dist/hybx/openapi-placeholder-contract.test.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ export * from './hybx/HttpHybxClient';
|
|||||||
export * from './reconciliation/ReconciliationStatus';
|
export * from './reconciliation/ReconciliationStatus';
|
||||||
export * from './resilience/circuitBreaker';
|
export * from './resilience/circuitBreaker';
|
||||||
export * from './omnl/omnl-api-auth';
|
export * from './omnl/omnl-api-auth';
|
||||||
|
export * from './payments/CronosFacilitatorAdapter';
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ describe('HttpHybxClient', () => {
|
|||||||
maxRetries: 1,
|
maxRetries: 1,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
/refuses production/
|
/production requires HYBX_UNIFIED_API_OK=1/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,289 @@
|
|||||||
|
import { describe, it } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {
|
||||||
|
CronosFacilitatorAdapter,
|
||||||
|
type FacilitatorClientLike,
|
||||||
|
type FacilitatorSettleResponse,
|
||||||
|
type PaymentIntent,
|
||||||
|
type SignedPayment,
|
||||||
|
} from './CronosFacilitatorAdapter';
|
||||||
|
|
||||||
|
class StubFacilitatorClient implements FacilitatorClientLike {
|
||||||
|
lastRequirementsInput?: Record<string, unknown>;
|
||||||
|
lastHeaderInput?: Record<string, unknown>;
|
||||||
|
lastVerifyRequest?: SignedPayment & { x402Version: number };
|
||||||
|
lastSettleRequest?: SignedPayment & { x402Version: number };
|
||||||
|
verifyResponse = { isValid: true, invalidReason: null as string | null };
|
||||||
|
settleResponse: FacilitatorSettleResponse = {
|
||||||
|
x402Version: 1,
|
||||||
|
event: 'payment.settled',
|
||||||
|
txHash: '0xsettled',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
timestamp: '2026-07-10T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
async getSupported() {
|
||||||
|
return {
|
||||||
|
kinds: [
|
||||||
|
{ x402Version: 1, scheme: 'exact', network: 'cronos-testnet' },
|
||||||
|
{ x402Version: 1, scheme: 'exact', network: 'cronos-mainnet' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePaymentRequirements(input: {
|
||||||
|
payTo: string;
|
||||||
|
asset?: string;
|
||||||
|
description?: string;
|
||||||
|
maxAmountRequired?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
maxTimeoutSeconds?: number;
|
||||||
|
resource?: string;
|
||||||
|
extra?: Record<string, unknown>;
|
||||||
|
outputSchema?: Record<string, unknown>;
|
||||||
|
}) {
|
||||||
|
this.lastRequirementsInput = input;
|
||||||
|
return {
|
||||||
|
scheme: 'exact' as const,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
payTo: input.payTo,
|
||||||
|
asset: input.asset ?? '0xasset',
|
||||||
|
description: input.description ?? 'default',
|
||||||
|
mimeType: input.mimeType ?? 'application/json',
|
||||||
|
maxAmountRequired: input.maxAmountRequired ?? '0',
|
||||||
|
maxTimeoutSeconds: input.maxTimeoutSeconds ?? 300,
|
||||||
|
resource: input.resource,
|
||||||
|
extra: input.extra,
|
||||||
|
outputSchema: input.outputSchema,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async generatePaymentHeader(input: {
|
||||||
|
to: string;
|
||||||
|
value: string;
|
||||||
|
asset?: string;
|
||||||
|
signer: unknown;
|
||||||
|
validAfter?: number;
|
||||||
|
validBefore?: number;
|
||||||
|
}) {
|
||||||
|
this.lastHeaderInput = input;
|
||||||
|
return 'base64-header';
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyPayment(request: SignedPayment & { x402Version: number }) {
|
||||||
|
this.lastVerifyRequest = request;
|
||||||
|
return this.verifyResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
async settlePayment(request: SignedPayment & { x402Version: number }) {
|
||||||
|
this.lastSettleRequest = request;
|
||||||
|
return this.settleResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildIntent(overrides: Partial<PaymentIntent> = {}): PaymentIntent {
|
||||||
|
return {
|
||||||
|
id: 'intent-1',
|
||||||
|
resource: '/api/paid',
|
||||||
|
description: 'Premium route access',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
payTo: '0xpayee',
|
||||||
|
price: {
|
||||||
|
asset: '0xasset-from-intent',
|
||||||
|
amountBaseUnits: '1000000',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
maxTimeoutSeconds: 300,
|
||||||
|
metadata: {
|
||||||
|
tenantId: 'tenant-a',
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CronosFacilitatorAdapter', () => {
|
||||||
|
it('returns deduplicated capabilities and configured asset', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
});
|
||||||
|
|
||||||
|
const capabilities = await adapter.getCapabilities();
|
||||||
|
|
||||||
|
assert.deepEqual(capabilities, {
|
||||||
|
schemes: ['exact'],
|
||||||
|
assets: ['0xconfigured-asset'],
|
||||||
|
supportsSettlement: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a challenge with normalized base-unit amount and metadata', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
});
|
||||||
|
|
||||||
|
const challenge = await adapter.createChallenge(buildIntent());
|
||||||
|
|
||||||
|
assert.equal(challenge.scheme, 'exact');
|
||||||
|
assert.equal(challenge.network, 'cronos-testnet');
|
||||||
|
assert.equal(challenge.paymentRequirements.maxAmountRequired, '1000000');
|
||||||
|
assert.equal(challenge.paymentRequirements.asset, '0xconfigured-asset');
|
||||||
|
assert.deepEqual(facilitator.lastRequirementsInput, {
|
||||||
|
payTo: '0xpayee',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
description: 'Premium route access',
|
||||||
|
maxAmountRequired: '1000000',
|
||||||
|
maxTimeoutSeconds: 300,
|
||||||
|
resource: '/api/paid',
|
||||||
|
extra: {
|
||||||
|
intentId: 'intent-1',
|
||||||
|
decimals: 6,
|
||||||
|
tenantId: 'tenant-a',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signs through the facilitator header generator with exact base units', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
});
|
||||||
|
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
validAfter: 10,
|
||||||
|
validBefore: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(signed.paymentHeader, 'base64-header');
|
||||||
|
assert.deepEqual(facilitator.lastHeaderInput, {
|
||||||
|
to: '0xpayee',
|
||||||
|
value: '1000000',
|
||||||
|
asset: '0xconfigured-asset',
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
validAfter: 10,
|
||||||
|
validBefore: 20,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies and returns normalized payment context', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
facilitator.verifyResponse = {
|
||||||
|
isValid: false,
|
||||||
|
invalidReason: 'authorization expired',
|
||||||
|
};
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
});
|
||||||
|
const verified = await adapter.verifyPayment(signed);
|
||||||
|
|
||||||
|
assert.equal(verified.ok, false);
|
||||||
|
assert.equal(verified.reason, 'authorization expired');
|
||||||
|
assert.deepEqual(verified.normalized, {
|
||||||
|
to: '0xpayee',
|
||||||
|
asset: '0xasset-from-intent',
|
||||||
|
amountBaseUnits: '1000000',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
assert.equal((facilitator.lastVerifyRequest as { x402Version: number }).x402Version, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps settlement success into a rail-level result', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
facilitator.settleResponse = {
|
||||||
|
x402Version: 1,
|
||||||
|
event: 'payment.settled',
|
||||||
|
txHash: '0xabc',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
timestamp: '2026-07-10T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
});
|
||||||
|
const settled = await adapter.settlePayment(signed);
|
||||||
|
|
||||||
|
assert.equal(settled.ok, true);
|
||||||
|
assert.equal(settled.txHash, '0xabc');
|
||||||
|
assert.equal(settled.network, 'cronos-testnet');
|
||||||
|
assert.equal((facilitator.lastSettleRequest as { x402Version: number }).x402Version, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps non-settled facilitator events into failures', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
facilitator.settleResponse = {
|
||||||
|
x402Version: 1,
|
||||||
|
event: 'payment.failed',
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
timestamp: '2026-07-10T00:00:00.000Z',
|
||||||
|
error: 'insufficient allowance',
|
||||||
|
};
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
|
||||||
|
const signed = await adapter.signPayment({
|
||||||
|
intent: buildIntent(),
|
||||||
|
signer: { id: 'signer-1' },
|
||||||
|
});
|
||||||
|
const settled = await adapter.settlePayment(signed);
|
||||||
|
|
||||||
|
assert.equal(settled.ok, false);
|
||||||
|
assert.equal(settled.error, 'insufficient allowance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects non-base-unit amounts before calling the facilitator', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-testnet',
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() =>
|
||||||
|
adapter.createChallenge(
|
||||||
|
buildIntent({
|
||||||
|
price: {
|
||||||
|
asset: '0xasset',
|
||||||
|
amountBaseUnits: '1.5',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
/amountBaseUnits must be an unsigned base-unit integer/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects network mismatches before signing or settlement', async () => {
|
||||||
|
const facilitator = new StubFacilitatorClient();
|
||||||
|
const adapter = new CronosFacilitatorAdapter({
|
||||||
|
facilitator,
|
||||||
|
network: 'cronos-mainnet',
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => adapter.signPayment({ intent: buildIntent(), signer: { id: 'signer-1' } }),
|
||||||
|
/network mismatch/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
export type ExactPaymentScheme = 'exact';
|
||||||
|
|
||||||
|
export interface PaymentMoney {
|
||||||
|
asset: string;
|
||||||
|
amountBaseUnits: string;
|
||||||
|
decimals: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentIntent {
|
||||||
|
id: string;
|
||||||
|
resource: string;
|
||||||
|
description: string;
|
||||||
|
network: string;
|
||||||
|
payTo: string;
|
||||||
|
price: PaymentMoney;
|
||||||
|
maxTimeoutSeconds: number;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentChallenge {
|
||||||
|
x402Version: number;
|
||||||
|
scheme: ExactPaymentScheme;
|
||||||
|
network: string;
|
||||||
|
paymentRequirements: FacilitatorPaymentRequirements;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignedPayment {
|
||||||
|
paymentHeader: string;
|
||||||
|
paymentRequirements: FacilitatorPaymentRequirements;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VerifyResult {
|
||||||
|
ok: boolean;
|
||||||
|
reason?: string;
|
||||||
|
normalized?: {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
asset?: string;
|
||||||
|
amountBaseUnits?: string;
|
||||||
|
network?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettlementResult {
|
||||||
|
ok: boolean;
|
||||||
|
txHash?: string;
|
||||||
|
network: string;
|
||||||
|
timestamp?: string;
|
||||||
|
error?: string;
|
||||||
|
raw?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentRailCapabilities {
|
||||||
|
schemes: string[];
|
||||||
|
assets: string[];
|
||||||
|
supportsSettlement: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentRailAdapter {
|
||||||
|
readonly id: string;
|
||||||
|
readonly network: string;
|
||||||
|
|
||||||
|
getCapabilities(): Promise<PaymentRailCapabilities>;
|
||||||
|
createChallenge(intent: PaymentIntent): Promise<PaymentChallenge>;
|
||||||
|
signPayment(input: SignPaymentInput): Promise<SignedPayment>;
|
||||||
|
verifyPayment(input: SignedPayment): Promise<VerifyResult>;
|
||||||
|
settlePayment(input: SignedPayment): Promise<SettlementResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignPaymentInput {
|
||||||
|
intent: PaymentIntent;
|
||||||
|
signer: unknown;
|
||||||
|
validAfter?: number;
|
||||||
|
validBefore?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorKind {
|
||||||
|
x402Version: number;
|
||||||
|
scheme: string;
|
||||||
|
network: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorSupportedResponse {
|
||||||
|
kinds: FacilitatorKind[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorVerifyResponse {
|
||||||
|
isValid: boolean;
|
||||||
|
invalidReason: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorSettleResponse {
|
||||||
|
x402Version: number;
|
||||||
|
event: string;
|
||||||
|
txHash?: string;
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
value?: string;
|
||||||
|
network: string;
|
||||||
|
timestamp: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorPaymentRequirements {
|
||||||
|
scheme: ExactPaymentScheme;
|
||||||
|
network: string;
|
||||||
|
payTo: string;
|
||||||
|
asset: string;
|
||||||
|
description: string;
|
||||||
|
mimeType: string;
|
||||||
|
maxAmountRequired: string;
|
||||||
|
maxTimeoutSeconds: number;
|
||||||
|
resource?: string;
|
||||||
|
extra?: Record<string, unknown>;
|
||||||
|
outputSchema?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorVerifyRequest {
|
||||||
|
x402Version: number;
|
||||||
|
paymentHeader: string;
|
||||||
|
paymentRequirements: FacilitatorPaymentRequirements;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitatorClientLike {
|
||||||
|
getSupported(): Promise<FacilitatorSupportedResponse>;
|
||||||
|
generatePaymentRequirements(input: {
|
||||||
|
payTo: string;
|
||||||
|
asset?: string;
|
||||||
|
description?: string;
|
||||||
|
maxAmountRequired?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
maxTimeoutSeconds?: number;
|
||||||
|
resource?: string;
|
||||||
|
extra?: Record<string, unknown>;
|
||||||
|
outputSchema?: Record<string, unknown>;
|
||||||
|
}): FacilitatorPaymentRequirements;
|
||||||
|
generatePaymentHeader(input: {
|
||||||
|
to: string;
|
||||||
|
value: string;
|
||||||
|
asset?: string;
|
||||||
|
signer: unknown;
|
||||||
|
validAfter?: number;
|
||||||
|
validBefore?: number;
|
||||||
|
}): Promise<string>;
|
||||||
|
verifyPayment(request: FacilitatorVerifyRequest): Promise<FacilitatorVerifyResponse>;
|
||||||
|
settlePayment(request: FacilitatorVerifyRequest): Promise<FacilitatorSettleResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CronosFacilitatorAdapterOptions {
|
||||||
|
facilitator: FacilitatorClientLike;
|
||||||
|
network: string;
|
||||||
|
asset?: string;
|
||||||
|
defaultDecimals?: number;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SCHEME: ExactPaymentScheme = 'exact';
|
||||||
|
const DEFAULT_DECIMALS = 6;
|
||||||
|
const SETTLED_EVENT = 'payment.settled';
|
||||||
|
|
||||||
|
function assertBaseUnitAmount(amountBaseUnits: string): void {
|
||||||
|
if (!/^[0-9]+$/.test(amountBaseUnits)) {
|
||||||
|
throw new Error(`PaymentIntent.price.amountBaseUnits must be an unsigned base-unit integer, got: ${amountBaseUnits}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNetworkMatch(expectedNetwork: string, actualNetwork: string): void {
|
||||||
|
if (expectedNetwork !== actualNetwork) {
|
||||||
|
throw new Error(`PaymentIntent network mismatch: expected ${expectedNetwork}, got ${actualNetwork}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CronosFacilitatorAdapter implements PaymentRailAdapter {
|
||||||
|
readonly id: string;
|
||||||
|
readonly network: string;
|
||||||
|
|
||||||
|
private readonly facilitator: FacilitatorClientLike;
|
||||||
|
private readonly asset?: string;
|
||||||
|
private readonly defaultDecimals: number;
|
||||||
|
|
||||||
|
constructor(options: CronosFacilitatorAdapterOptions) {
|
||||||
|
this.id = options.id ?? 'cronos-facilitator';
|
||||||
|
this.network = options.network;
|
||||||
|
this.facilitator = options.facilitator;
|
||||||
|
this.asset = options.asset;
|
||||||
|
this.defaultDecimals = options.defaultDecimals ?? DEFAULT_DECIMALS;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCapabilities(): Promise<PaymentRailCapabilities> {
|
||||||
|
const supported = await this.facilitator.getSupported();
|
||||||
|
const schemes = [...new Set(supported.kinds.map((kind) => kind.scheme))];
|
||||||
|
const assets = this.asset ? [this.asset] : [];
|
||||||
|
return {
|
||||||
|
schemes,
|
||||||
|
assets,
|
||||||
|
supportsSettlement: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async createChallenge(intent: PaymentIntent): Promise<PaymentChallenge> {
|
||||||
|
this.assertIntent(intent);
|
||||||
|
const paymentRequirements = this.facilitator.generatePaymentRequirements({
|
||||||
|
payTo: intent.payTo,
|
||||||
|
asset: this.resolveAsset(intent),
|
||||||
|
description: intent.description,
|
||||||
|
maxAmountRequired: intent.price.amountBaseUnits,
|
||||||
|
maxTimeoutSeconds: intent.maxTimeoutSeconds,
|
||||||
|
resource: intent.resource,
|
||||||
|
extra: {
|
||||||
|
intentId: intent.id,
|
||||||
|
decimals: intent.price.decimals || this.defaultDecimals,
|
||||||
|
...intent.metadata,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
x402Version: 1,
|
||||||
|
scheme: DEFAULT_SCHEME,
|
||||||
|
network: this.network,
|
||||||
|
paymentRequirements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async signPayment(input: SignPaymentInput): Promise<SignedPayment> {
|
||||||
|
this.assertIntent(input.intent);
|
||||||
|
const challenge = await this.createChallenge(input.intent);
|
||||||
|
const paymentHeader = await this.facilitator.generatePaymentHeader({
|
||||||
|
to: input.intent.payTo,
|
||||||
|
value: input.intent.price.amountBaseUnits,
|
||||||
|
asset: this.resolveAsset(input.intent),
|
||||||
|
signer: input.signer,
|
||||||
|
validAfter: input.validAfter,
|
||||||
|
validBefore: input.validBefore,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentHeader,
|
||||||
|
paymentRequirements: challenge.paymentRequirements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyPayment(input: SignedPayment): Promise<VerifyResult> {
|
||||||
|
const response = await this.facilitator.verifyPayment(this.toVerifyRequest(input));
|
||||||
|
return {
|
||||||
|
ok: response.isValid,
|
||||||
|
reason: response.invalidReason ?? undefined,
|
||||||
|
normalized: {
|
||||||
|
to: input.paymentRequirements.payTo,
|
||||||
|
asset: input.paymentRequirements.asset,
|
||||||
|
amountBaseUnits: input.paymentRequirements.maxAmountRequired,
|
||||||
|
network: input.paymentRequirements.network,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async settlePayment(input: SignedPayment): Promise<SettlementResult> {
|
||||||
|
const response = await this.facilitator.settlePayment(this.toVerifyRequest(input));
|
||||||
|
return {
|
||||||
|
ok: response.event === SETTLED_EVENT,
|
||||||
|
txHash: response.txHash,
|
||||||
|
network: response.network,
|
||||||
|
timestamp: response.timestamp,
|
||||||
|
error: response.error,
|
||||||
|
raw: response,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toVerifyRequest(input: SignedPayment): FacilitatorVerifyRequest {
|
||||||
|
return {
|
||||||
|
x402Version: 1,
|
||||||
|
paymentHeader: input.paymentHeader,
|
||||||
|
paymentRequirements: input.paymentRequirements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveAsset(intent: PaymentIntent): string {
|
||||||
|
return this.asset ?? intent.price.asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertIntent(intent: PaymentIntent): void {
|
||||||
|
assertNetworkMatch(this.network, intent.network);
|
||||||
|
assertBaseUnitAmount(intent.price.amountBaseUnits);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,7 @@ contract ConfigureAtomicX402PolygonIntent is Script {
|
|||||||
defaultSettlementMode: SETTLEMENT_MODE
|
defaultSettlementMode: SETTLEMENT_MODE
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
coordinator.setAsyncDestinationCorridor(corridorId, true);
|
||||||
coordinator.grantRole(coordinator.INTENT_ADAPTER_ROLE(), sourceAdapter);
|
coordinator.grantRole(coordinator.INTENT_ADAPTER_ROLE(), sourceAdapter);
|
||||||
settlementRouter.setAdapter(SETTLEMENT_MODE, settlementAdapter);
|
settlementRouter.setAdapter(SETTLEMENT_MODE, settlementAdapter);
|
||||||
feePolicy.setCorridorPolicy(
|
feePolicy.setCorridorPolicy(
|
||||||
|
|||||||
138
script/bridge/atomic/DeployAtomicX402PolygonIntentStack.s.sol
Normal file
138
script/bridge/atomic/DeployAtomicX402PolygonIntentStack.s.sol
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import {Script, console} from "forge-std/Script.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicBridgeCoordinator.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicFeePolicy.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicFulfillerRegistry.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicLiquidityVault.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicObligationEscrow.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicQuoteEngine.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicSettlementRouter.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicSlashingManager.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicTypes.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/AtomicX402SourceAdapter.sol";
|
||||||
|
import "../../../contracts/bridge/atomic/CWMultiTokenBridgeSettlementAdapter.sol";
|
||||||
|
|
||||||
|
interface IRoleManagedToken {
|
||||||
|
function grantRole(bytes32 role, address account) external;
|
||||||
|
}
|
||||||
|
|
||||||
|
contract DeployAtomicX402PolygonIntentStack is Script {
|
||||||
|
uint64 internal constant SOURCE_CHAIN_138 = 138;
|
||||||
|
uint64 internal constant POLYGON_SELECTOR = 4051577828743386545;
|
||||||
|
|
||||||
|
address internal constant CUSDC_CHAIN138 = 0xf22258f57794CC8E06237084b353Ab30fFfa640b;
|
||||||
|
address internal constant CUSDC_V2_CHAIN138 = 0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d;
|
||||||
|
address internal constant CWUSDC_V2_POLYGON = 0x7476a64B37732698b9f729fdB373a7B65e9677e9;
|
||||||
|
address internal constant CW_MULTI_TOKEN_BRIDGE_L1 = 0x152eD3e9912161b76BDFd368D0C84B7C31C10dE7;
|
||||||
|
|
||||||
|
bytes32 internal constant SETTLEMENT_MODE = keccak256("CW_POLYGON_CUSDCV2_X402");
|
||||||
|
bytes32 internal constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||||
|
|
||||||
|
function run() external {
|
||||||
|
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||||
|
address deployer = vm.addr(pk);
|
||||||
|
address protocolTreasury = vm.envOr("ATOMIC_PROTOCOL_TREASURY", deployer);
|
||||||
|
address fulfiller = vm.envOr("ATOMIC_X402_FILLER", deployer);
|
||||||
|
|
||||||
|
uint256 maxNotional = vm.envOr("ATOMIC_X402_MAX_NOTIONAL", uint256(110_000e6));
|
||||||
|
uint16 maxReservedBps = uint16(vm.envOr("ATOMIC_X402_MAX_RESERVED_BPS", uint256(8_000)));
|
||||||
|
uint256 targetBuffer = vm.envOr("ATOMIC_X402_TARGET_BUFFER", uint256(0));
|
||||||
|
uint256 maxSettlementBacklog = vm.envOr("ATOMIC_X402_MAX_SETTLEMENT_BACKLOG", uint256(250_000e6));
|
||||||
|
uint16 maxOracleDriftBps = uint16(vm.envOr("ATOMIC_X402_MAX_ORACLE_DRIFT_BPS", uint256(100)));
|
||||||
|
uint256 fulfilmentTimeout = vm.envOr("ATOMIC_X402_FULFILMENT_TIMEOUT", uint256(30 minutes));
|
||||||
|
uint256 settlementTimeout = vm.envOr("ATOMIC_X402_SETTLEMENT_TIMEOUT", uint256(2 days));
|
||||||
|
|
||||||
|
uint16 fulfillerFeeBps = uint16(vm.envOr("ATOMIC_X402_FILLER_FEE_BPS", uint256(100)));
|
||||||
|
uint16 protocolFeeBps = uint16(vm.envOr("ATOMIC_X402_PROTOCOL_FEE_BPS", uint256(50)));
|
||||||
|
uint16 bondBps = uint16(vm.envOr("ATOMIC_X402_BOND_BPS", uint256(12_000)));
|
||||||
|
uint16 slashPenaltyBps = uint16(vm.envOr("ATOMIC_X402_SLASH_PENALTY_BPS", uint256(1_000)));
|
||||||
|
|
||||||
|
vm.startBroadcast(pk);
|
||||||
|
|
||||||
|
AtomicLiquidityVault vault = new AtomicLiquidityVault(deployer);
|
||||||
|
AtomicFulfillerRegistry registry = new AtomicFulfillerRegistry(CUSDC_V2_CHAIN138, deployer);
|
||||||
|
AtomicFeePolicy feePolicy = new AtomicFeePolicy(deployer);
|
||||||
|
AtomicObligationEscrow escrow = new AtomicObligationEscrow(deployer);
|
||||||
|
AtomicSettlementRouter router = new AtomicSettlementRouter(deployer);
|
||||||
|
AtomicSlashingManager slashingManager = new AtomicSlashingManager(address(registry), deployer);
|
||||||
|
AtomicBridgeCoordinator coordinator = new AtomicBridgeCoordinator(
|
||||||
|
address(vault),
|
||||||
|
address(registry),
|
||||||
|
address(escrow),
|
||||||
|
address(router),
|
||||||
|
address(feePolicy),
|
||||||
|
address(slashingManager),
|
||||||
|
protocolTreasury,
|
||||||
|
deployer
|
||||||
|
);
|
||||||
|
AtomicQuoteEngine quoteEngine =
|
||||||
|
new AtomicQuoteEngine(address(coordinator), address(vault), address(registry), address(feePolicy));
|
||||||
|
AtomicX402SourceAdapter sourceAdapter = new AtomicX402SourceAdapter(
|
||||||
|
CUSDC_CHAIN138,
|
||||||
|
CUSDC_V2_CHAIN138,
|
||||||
|
CWUSDC_V2_POLYGON,
|
||||||
|
address(coordinator),
|
||||||
|
SOURCE_CHAIN_138,
|
||||||
|
POLYGON_SELECTOR
|
||||||
|
);
|
||||||
|
CWMultiTokenBridgeSettlementAdapter settlementAdapter =
|
||||||
|
new CWMultiTokenBridgeSettlementAdapter(CW_MULTI_TOKEN_BRIDGE_L1, POLYGON_SELECTOR);
|
||||||
|
|
||||||
|
vault.grantRole(vault.COORDINATOR_ROLE(), address(coordinator));
|
||||||
|
vault.grantRole(vault.RECONCILER_ROLE(), address(coordinator));
|
||||||
|
registry.grantRole(registry.COORDINATOR_ROLE(), address(coordinator));
|
||||||
|
registry.grantRole(registry.SLASHER_ROLE(), address(slashingManager));
|
||||||
|
escrow.grantRole(escrow.COORDINATOR_ROLE(), address(coordinator));
|
||||||
|
router.grantRole(router.COORDINATOR_ROLE(), address(coordinator));
|
||||||
|
slashingManager.grantRole(slashingManager.COORDINATOR_ROLE(), address(coordinator));
|
||||||
|
coordinator.grantRole(coordinator.INTENT_ADAPTER_ROLE(), address(sourceAdapter));
|
||||||
|
IRoleManagedToken(CUSDC_V2_CHAIN138).grantRole(MINTER_ROLE, address(sourceAdapter));
|
||||||
|
|
||||||
|
bytes32 corridorId =
|
||||||
|
coordinator.getCorridorId(SOURCE_CHAIN_138, POLYGON_SELECTOR, CUSDC_V2_CHAIN138, CWUSDC_V2_POLYGON);
|
||||||
|
coordinator.configureCorridor(
|
||||||
|
AtomicTypes.CorridorConfig({
|
||||||
|
enabled: true,
|
||||||
|
degraded: false,
|
||||||
|
sourceChain: SOURCE_CHAIN_138,
|
||||||
|
destinationChain: POLYGON_SELECTOR,
|
||||||
|
assetIn: CUSDC_V2_CHAIN138,
|
||||||
|
assetOut: CWUSDC_V2_POLYGON,
|
||||||
|
maxNotional: maxNotional,
|
||||||
|
maxReservedBps: maxReservedBps,
|
||||||
|
targetBuffer: targetBuffer,
|
||||||
|
maxSettlementBacklog: maxSettlementBacklog,
|
||||||
|
maxOracleDriftBps: maxOracleDriftBps,
|
||||||
|
fulfilmentTimeout: fulfilmentTimeout,
|
||||||
|
settlementTimeout: settlementTimeout,
|
||||||
|
defaultSettlementMode: SETTLEMENT_MODE
|
||||||
|
})
|
||||||
|
);
|
||||||
|
coordinator.setAsyncDestinationCorridor(corridorId, true);
|
||||||
|
feePolicy.setCorridorPolicy(
|
||||||
|
corridorId, fulfillerFeeBps, protocolFeeBps, bondBps, slashPenaltyBps, fulfilmentTimeout, settlementTimeout
|
||||||
|
);
|
||||||
|
router.setAdapter(SETTLEMENT_MODE, address(settlementAdapter));
|
||||||
|
registry.setFulfillerActive(fulfiller, true);
|
||||||
|
registry.setCorridorAuthorization(fulfiller, corridorId, true);
|
||||||
|
|
||||||
|
vm.stopBroadcast();
|
||||||
|
|
||||||
|
console.log("AtomicLiquidityVault:", address(vault));
|
||||||
|
console.log("AtomicFulfillerRegistry:", address(registry));
|
||||||
|
console.log("AtomicFeePolicy:", address(feePolicy));
|
||||||
|
console.log("AtomicObligationEscrow:", address(escrow));
|
||||||
|
console.log("AtomicSettlementRouter:", address(router));
|
||||||
|
console.log("AtomicSlashingManager:", address(slashingManager));
|
||||||
|
console.log("AtomicBridgeCoordinator:", address(coordinator));
|
||||||
|
console.log("AtomicQuoteEngine:", address(quoteEngine));
|
||||||
|
console.log("AtomicX402SourceAdapter:", address(sourceAdapter));
|
||||||
|
console.log("CWMultiTokenBridgeSettlementAdapter:", address(settlementAdapter));
|
||||||
|
console.log("CorridorId:");
|
||||||
|
console.logBytes32(corridorId);
|
||||||
|
console.log("SettlementMode:");
|
||||||
|
console.logBytes32(SETTLEMENT_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
script/x402/DeployAtomicCwUsdcUsdcPurchase.s.sol
Normal file
30
script/x402/DeployAtomicCwUsdcUsdcPurchase.s.sol
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import {Script, console} from "forge-std/Script.sol";
|
||||||
|
import {AtomicCwUsdcUsdcPurchase} from "../../contracts/x402/AtomicCwUsdcUsdcPurchase.sol";
|
||||||
|
|
||||||
|
contract DeployAtomicCwUsdcUsdcPurchase is Script {
|
||||||
|
address internal constant POLYGON_CWUSDC_V2 = 0x7476a64B37732698b9f729fdB373a7B65e9677e9;
|
||||||
|
address internal constant POLYGON_USDC = 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359;
|
||||||
|
|
||||||
|
function run() external returns (AtomicCwUsdcUsdcPurchase purchase) {
|
||||||
|
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||||
|
address deployer = vm.addr(pk);
|
||||||
|
address treasury = vm.envOr("X402_POLYGON_ATOMIC_TREASURY", vm.envOr("SERVER_WALLET_ADDRESS", deployer));
|
||||||
|
address owner = vm.envOr("X402_POLYGON_ATOMIC_OWNER", deployer);
|
||||||
|
|
||||||
|
console.log("Deployer:", deployer);
|
||||||
|
console.log("cWUSDC_V2:", POLYGON_CWUSDC_V2);
|
||||||
|
console.log("USDC:", POLYGON_USDC);
|
||||||
|
console.log("Treasury:", treasury);
|
||||||
|
console.log("Owner:", owner);
|
||||||
|
|
||||||
|
vm.startBroadcast(pk);
|
||||||
|
purchase = new AtomicCwUsdcUsdcPurchase(POLYGON_CWUSDC_V2, POLYGON_USDC, treasury, owner);
|
||||||
|
vm.stopBroadcast();
|
||||||
|
|
||||||
|
console.log("AtomicCwUsdcUsdcPurchase:", address(purchase));
|
||||||
|
console.log("Contract treasury:", purchase.treasury());
|
||||||
|
}
|
||||||
|
}
|
||||||
30
script/x402/DeployAtomicCwUsdcUsdcPurchaseV2.s.sol
Normal file
30
script/x402/DeployAtomicCwUsdcUsdcPurchaseV2.s.sol
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import {Script, console} from "forge-std/Script.sol";
|
||||||
|
import {AtomicCwUsdcUsdcPurchaseV2} from "../../contracts/x402/AtomicCwUsdcUsdcPurchaseV2.sol";
|
||||||
|
|
||||||
|
contract DeployAtomicCwUsdcUsdcPurchaseV2 is Script {
|
||||||
|
address internal constant POLYGON_CWUSDC_V2 = 0x7476a64B37732698b9f729fdB373a7B65e9677e9;
|
||||||
|
address internal constant POLYGON_USDC = 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359;
|
||||||
|
|
||||||
|
function run() external returns (AtomicCwUsdcUsdcPurchaseV2 purchase) {
|
||||||
|
uint256 pk = vm.envUint("PRIVATE_KEY");
|
||||||
|
address deployer = vm.addr(pk);
|
||||||
|
address treasury = vm.envOr("X402_POLYGON_ATOMIC_TREASURY", vm.envOr("SERVER_WALLET_ADDRESS", deployer));
|
||||||
|
address owner = vm.envOr("X402_POLYGON_ATOMIC_OWNER", deployer);
|
||||||
|
|
||||||
|
console.log("Deployer:", deployer);
|
||||||
|
console.log("cWUSDC_V2:", POLYGON_CWUSDC_V2);
|
||||||
|
console.log("USDC:", POLYGON_USDC);
|
||||||
|
console.log("Treasury:", treasury);
|
||||||
|
console.log("Owner:", owner);
|
||||||
|
|
||||||
|
vm.startBroadcast(pk);
|
||||||
|
purchase = new AtomicCwUsdcUsdcPurchaseV2(POLYGON_CWUSDC_V2, POLYGON_USDC, treasury, owner);
|
||||||
|
vm.stopBroadcast();
|
||||||
|
|
||||||
|
console.log("AtomicCwUsdcUsdcPurchaseV2:", address(purchase));
|
||||||
|
console.log("Contract treasury:", purchase.treasury());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ verify_one() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
set +e
|
set +e
|
||||||
out="$(forge verify-contract \
|
out="$(FOUNDRY_PROFILE=bsc_tokens_verify forge verify-contract \
|
||||||
--chain-id 1 \
|
--chain-id 1 \
|
||||||
--num-of-optimizations 200 \
|
--num-of-optimizations 200 \
|
||||||
--via-ir \
|
--via-ir \
|
||||||
@@ -87,7 +87,7 @@ export FOUNDRY_OUT="out/scopes/tokens"
|
|||||||
export FOUNDRY_CACHE_PATH="cache/scopes/tokens"
|
export FOUNDRY_CACHE_PATH="cache/scopes/tokens"
|
||||||
|
|
||||||
if ! $DRY_RUN; then
|
if ! $DRY_RUN; then
|
||||||
bash "$PROJECT_ROOT/scripts/forge/scope.sh" build tokens -q
|
FOUNDRY_PROFILE=bsc_tokens_verify bash "$PROJECT_ROOT/scripts/forge/scope.sh" build tokens -q
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== Verify Mainnet CompliantWrappedToken (admin=$ADMIN) ==="
|
echo "=== Verify Mainnet CompliantWrappedToken (admin=$ADMIN) ==="
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
"version": {
|
"version": {
|
||||||
"major": 1,
|
"major": 1,
|
||||||
"minor": 6,
|
"minor": 6,
|
||||||
"patch": 10
|
"patch": 13
|
||||||
},
|
},
|
||||||
"timestamp": "2026-06-18T18:27:15.126Z",
|
"timestamp": "2026-07-08T02:49:17.147Z",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"dbis",
|
"dbis",
|
||||||
"chain138",
|
"chain138",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/caudc.svg",
|
"logoURI": "https://d-bis.org/tokens/caudc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "AUD",
|
"currency": "AUD",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/ccadc.svg",
|
"logoURI": "https://d-bis.org/tokens/ccadc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "CAD",
|
"currency": "CAD",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cchfc.svg",
|
"logoURI": "https://d-bis.org/tokens/cchfc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "CHF",
|
"currency": "CHF",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/ceurc.svg",
|
"logoURI": "https://d-bis.org/tokens/ceurc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "EUR",
|
"currency": "EUR",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -307,7 +307,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/ceurt.svg",
|
"logoURI": "https://d-bis.org/tokens/ceurt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -316,7 +316,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "EUR",
|
"currency": "EUR",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -335,7 +335,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cgbpc.svg",
|
"logoURI": "https://d-bis.org/tokens/cgbpc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "GBP",
|
"currency": "GBP",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -363,7 +363,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cgbpt.svg",
|
"logoURI": "https://d-bis.org/tokens/cgbpt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -372,7 +372,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "GBP",
|
"currency": "GBP",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cjpyc.svg",
|
"logoURI": "https://d-bis.org/tokens/cjpyc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -400,7 +400,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "tokenized-fiat",
|
"category": "tokenized-fiat",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "JPY",
|
"currency": "JPY",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -438,12 +438,12 @@
|
|||||||
{
|
{
|
||||||
"chainId": 138,
|
"chainId": 138,
|
||||||
"address": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
|
"address": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
|
||||||
"name": "USD Coin (Compliant)",
|
"name": "USD Cash Electronic Money (Compliant)",
|
||||||
"symbol": "cUSDC",
|
"symbol": "cUSDC",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cusdc.svg",
|
"logoURI": "https://d-bis.org/tokens/cusdc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -452,7 +452,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "gru-emoney",
|
"category": "gru-emoney",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -472,12 +472,12 @@
|
|||||||
{
|
{
|
||||||
"chainId": 138,
|
"chainId": 138,
|
||||||
"address": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
|
"address": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
|
||||||
"name": "Tether USD (Compliant)",
|
"name": "Compliant USD Treasury eMoney",
|
||||||
"symbol": "cUSDT",
|
"symbol": "cUSDT",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cusdt.svg",
|
"logoURI": "https://d-bis.org/tokens/cusdt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "gru-emoney",
|
"category": "gru-emoney",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -561,7 +561,7 @@
|
|||||||
{
|
{
|
||||||
"chainId": 138,
|
"chainId": 138,
|
||||||
"address": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E",
|
"address": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E",
|
||||||
"name": "Tether XAU (Compliant)",
|
"name": "Compliant XAU eMoney",
|
||||||
"symbol": "cXAUT",
|
"symbol": "cXAUT",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cxaut.svg",
|
"logoURI": "https://d-bis.org/tokens/cxaut.svg",
|
||||||
@@ -808,12 +808,12 @@
|
|||||||
{
|
{
|
||||||
"chainId": 138,
|
"chainId": 138,
|
||||||
"address": "0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d",
|
"address": "0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d",
|
||||||
"name": "USD Coin (Compliant V2)",
|
"name": "Compliant USD cash eMoney (V2)",
|
||||||
"symbol": "cUSDC_V2",
|
"symbol": "cUSDC_V2",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cusdc_v2.svg",
|
"logoURI": "https://d-bis.org/tokens/cusdc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -823,7 +823,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "gru-emoney",
|
"category": "gru-emoney",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -843,12 +843,12 @@
|
|||||||
{
|
{
|
||||||
"chainId": 138,
|
"chainId": 138,
|
||||||
"address": "0x9FBfab33882Efe0038DAa608185718b772EE5660",
|
"address": "0x9FBfab33882Efe0038DAa608185718b772EE5660",
|
||||||
"name": "Tether USD (Compliant V2)",
|
"name": "Compliant USD Treasury eMoney (V2)",
|
||||||
"symbol": "cUSDT_V2",
|
"symbol": "cUSDT_V2",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cusdt_v2.svg",
|
"logoURI": "https://d-bis.org/tokens/cusdt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -858,7 +858,7 @@
|
|||||||
],
|
],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"category": "gru-emoney",
|
"category": "gru-emoney",
|
||||||
"instrument": "emoney-or-fiat-backed-stablecoin",
|
"instrument": "gru-m1-emoney",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"settlement": "fiat",
|
"settlement": "fiat",
|
||||||
"cashLike": true,
|
"cashLike": true,
|
||||||
@@ -885,10 +885,6 @@
|
|||||||
"name": "Wrapped",
|
"name": "Wrapped",
|
||||||
"description": "Wrapped tokens representing assets"
|
"description": "Wrapped tokens representing assets"
|
||||||
},
|
},
|
||||||
"stablecoin": {
|
|
||||||
"name": "Stablecoin",
|
|
||||||
"description": "Stable value tokens pegged to fiat"
|
|
||||||
},
|
|
||||||
"compliant": {
|
"compliant": {
|
||||||
"name": "Compliant",
|
"name": "Compliant",
|
||||||
"description": "Regulatory compliant assets"
|
"description": "Regulatory compliant assets"
|
||||||
@@ -920,6 +916,10 @@
|
|||||||
"ccip": {
|
"ccip": {
|
||||||
"name": "CCIP",
|
"name": "CCIP",
|
||||||
"description": "Cross Chain Interoperability Protocol tokens"
|
"description": "Cross Chain Interoperability Protocol tokens"
|
||||||
|
},
|
||||||
|
"emoney": {
|
||||||
|
"name": "eMoney",
|
||||||
|
"description": "GRU M1 electronic money instruments"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"ethereum",
|
"ethereum",
|
||||||
"mainnet",
|
"mainnet",
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"gru",
|
"gru",
|
||||||
"dbis",
|
"dbis",
|
||||||
"cwusdc",
|
"cwusdc",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
{
|
{
|
||||||
"chainId": 1,
|
"chainId": 1,
|
||||||
"address": "0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE",
|
"address": "0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE",
|
||||||
"name": "Wrapped cUSDT",
|
"name": "Wrapped cUSDT eMoney (GRU transport)",
|
||||||
"symbol": "cWUSDT",
|
"symbol": "cWUSDT",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwusdt.svg",
|
"logoURI": "https://d-bis.org/tokens/cwusdt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
{
|
{
|
||||||
"chainId": 1,
|
"chainId": 1,
|
||||||
"address": "0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a",
|
"address": "0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a",
|
||||||
"name": "Wrapped cUSDC",
|
"name": "Wrapped cUSDC eMoney (GRU transport)",
|
||||||
"symbol": "cWUSDC",
|
"symbol": "cWUSDC",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwusdc.svg",
|
"logoURI": "https://d-bis.org/tokens/cwusdc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cweurc.svg",
|
"logoURI": "https://d-bis.org/tokens/cweurc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cweurt.svg",
|
"logoURI": "https://d-bis.org/tokens/cweurt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwgbpc.svg",
|
"logoURI": "https://d-bis.org/tokens/cwgbpc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwgbpt.svg",
|
"logoURI": "https://d-bis.org/tokens/cwgbpt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwaudc.svg",
|
"logoURI": "https://d-bis.org/tokens/cwaudc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwjpyc.svg",
|
"logoURI": "https://d-bis.org/tokens/cwjpyc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwchfc.svg",
|
"logoURI": "https://d-bis.org/tokens/cwchfc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwcadc.svg",
|
"logoURI": "https://d-bis.org/tokens/cwcadc.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -386,7 +386,7 @@
|
|||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
"logoURI": "https://d-bis.org/tokens/cwausdt.svg",
|
"logoURI": "https://d-bis.org/tokens/cwausdt.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"stablecoin",
|
"emoney",
|
||||||
"defi",
|
"defi",
|
||||||
"compliant",
|
"compliant",
|
||||||
"fiat",
|
"fiat",
|
||||||
@@ -406,10 +406,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"stablecoin": {
|
|
||||||
"name": "Stablecoin",
|
|
||||||
"description": "Stable value tokens pegged to fiat"
|
|
||||||
},
|
|
||||||
"defi": {
|
"defi": {
|
||||||
"name": "DeFi",
|
"name": "DeFi",
|
||||||
"description": "Decentralized Finance tokens"
|
"description": "Decentralized Finance tokens"
|
||||||
@@ -433,6 +429,10 @@
|
|||||||
"wrapped": {
|
"wrapped": {
|
||||||
"name": "Wrapped",
|
"name": "Wrapped",
|
||||||
"description": "Public network wrapped transport mirrors of hub assets"
|
"description": "Public network wrapped transport mirrors of hub assets"
|
||||||
|
},
|
||||||
|
"emoney": {
|
||||||
|
"name": "eMoney",
|
||||||
|
"description": "GRU M1 electronic money wrapped transport mirrors"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Request, Response, NextFunction } from 'express';
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { createComplianceDecisionEngine } from '../../compliance/volume13-engine.js';
|
import { createComplianceDecisionEngine } from '../../compliance/volume13-engine';
|
||||||
|
|
||||||
const PROXMOX_ROOT = process.env.PROXMOX_ROOT || path.resolve(process.cwd(), '../../../..');
|
const PROXMOX_ROOT = process.env.PROXMOX_ROOT || path.resolve(process.cwd(), '../../../..');
|
||||||
const engine = createComplianceDecisionEngine(PROXMOX_ROOT);
|
const engine = createComplianceDecisionEngine(PROXMOX_ROOT);
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ describe('Config API runtime networks loader', () => {
|
|||||||
expect(metamaskBody.addEthereumChain).toMatchObject({
|
expect(metamaskBody.addEthereumChain).toMatchObject({
|
||||||
chainId: '0x8a',
|
chainId: '0x8a',
|
||||||
chainName: 'Defi Oracle Meta Mainnet',
|
chainName: 'Defi Oracle Meta Mainnet',
|
||||||
rpcUrls: ['https://rpc.public-0138.defi-oracle.io'],
|
rpcUrls: ['https://rpc-http-pub.d-bis.org'],
|
||||||
});
|
});
|
||||||
expect(metamaskBody.addEthereumChain).not.toHaveProperty('chainIdDecimal');
|
expect(metamaskBody.addEthereumChain).not.toHaveProperty('chainIdDecimal');
|
||||||
expect(metamaskBody.addEthereumChain).not.toHaveProperty('oracles');
|
expect(metamaskBody.addEthereumChain).not.toHaveProperty('oracles');
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import express from 'express';
|
|||||||
import reportRoutes from './report';
|
import reportRoutes from './report';
|
||||||
import { getCanonicalTokenBySymbol } from '../../config/canonical-tokens';
|
import { getCanonicalTokenBySymbol } from '../../config/canonical-tokens';
|
||||||
|
|
||||||
|
jest.setTimeout(30_000);
|
||||||
|
|
||||||
jest.mock('../../database/repositories/token-repo', () => ({
|
jest.mock('../../database/repositories/token-repo', () => ({
|
||||||
TokenRepository: jest.fn().mockImplementation(() => ({
|
TokenRepository: jest.fn().mockImplementation(() => ({
|
||||||
getToken: jest.fn().mockResolvedValue(null),
|
getToken: jest.fn().mockResolvedValue(null),
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ describe('Tokens API', () => {
|
|||||||
symbol: 'WETH',
|
symbol: 'WETH',
|
||||||
decimals: 18,
|
decimals: 18,
|
||||||
market: expect.objectContaining({
|
market: expect.objectContaining({
|
||||||
priceUsd: 2490,
|
priceUsd: 1680,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ describe('Tokens API', () => {
|
|||||||
symbol: 'WETH10',
|
symbol: 'WETH10',
|
||||||
decimals: 18,
|
decimals: 18,
|
||||||
market: expect.objectContaining({
|
market: expect.objectContaining({
|
||||||
priceUsd: 2490,
|
priceUsd: 1680,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -223,7 +223,7 @@ describe('Tokens API', () => {
|
|||||||
symbol: 'WETH10',
|
symbol: 'WETH10',
|
||||||
decimals: 18,
|
decimals: 18,
|
||||||
market: expect.objectContaining({
|
market: expect.objectContaining({
|
||||||
priceUsd: 2490,
|
priceUsd: 1680,
|
||||||
volume24h: 1234,
|
volume24h: 1234,
|
||||||
liquidityUsd: 3456,
|
liquidityUsd: 3456,
|
||||||
}),
|
}),
|
||||||
@@ -349,7 +349,7 @@ describe('Tokens API', () => {
|
|||||||
current: {
|
current: {
|
||||||
chainId: 138,
|
chainId: 138,
|
||||||
tokenAddress: wethAddress,
|
tokenAddress: wethAddress,
|
||||||
priceUsd: 2490,
|
priceUsd: 1680,
|
||||||
stale: false,
|
stale: false,
|
||||||
},
|
},
|
||||||
historical: {
|
historical: {
|
||||||
|
|||||||
@@ -486,11 +486,11 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
|
|||||||
{ symbol: 'USDC', name: 'USD Coin (Official Mirror)', type: 'base', decimals: 6, currencyCode: 'USD', addresses: { [CHAIN_138]: addr('USDC', CHAIN_138) || '' } },
|
{ symbol: 'USDC', name: 'USD Coin (Official Mirror)', type: 'base', decimals: 6, currencyCode: 'USD', addresses: { [CHAIN_138]: addr('USDC', CHAIN_138) || '' } },
|
||||||
{ symbol: 'USDT', name: 'Tether USD (Official Mirror)', type: 'base', decimals: 6, currencyCode: 'USD', addresses: { [CHAIN_138]: addr('USDT', CHAIN_138) || '' } },
|
{ symbol: 'USDT', name: 'Tether USD (Official Mirror)', type: 'base', decimals: 6, currencyCode: 'USD', addresses: { [CHAIN_138]: addr('USDT', CHAIN_138) || '' } },
|
||||||
// Chain 138 v0 only (no X): cUSDC on 138; cXUSDC used only for bridged/origin reference elsewhere. See ISO4217_COMPLIANT_TOKEN_MATRIX.md
|
// Chain 138 v0 only (no X): cUSDC on 138; cXUSDC used only for bridged/origin reference elsewhere. See ISO4217_COMPLIANT_TOKEN_MATRIX.md
|
||||||
{ symbol: 'cUSDC', name: 'USD Coin (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', v0Alias: 'cUSDC', addresses: { [CHAIN_138]: addr('cUSDC', CHAIN_138) || '', [CHAIN_651940]: addr('cUSDC', CHAIN_651940) || '', ...Object.fromEntries(L2_CHAIN_IDS.map((id) => [id, addr('cUSDC', id)])) } },
|
{ symbol: 'cUSDC', name: 'USD Cash Electronic Money (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', v0Alias: 'cUSDC', addresses: { [CHAIN_138]: addr('cUSDC', CHAIN_138) || '', [CHAIN_651940]: addr('cUSDC', CHAIN_651940) || '', ...Object.fromEntries(L2_CHAIN_IDS.map((id) => [id, addr('cUSDC', id)])) } },
|
||||||
{ symbol: 'cUSDC_V2', name: 'USD Coin (Compliant V2)', type: 'base', decimals: 6, currencyCode: 'USD', familySymbol: 'cUSDC', deploymentVersion: 'v2', deploymentStatus: 'staged', preferredForX402: true, liquiditySourceSymbol: 'cUSDC', description: 'Chain 138 x402 / permit-capable V2 deployment. Liquidity and PMM routing remain on cUSDC until cutover.', addresses: { [CHAIN_138]: addr('cUSDC_V2', CHAIN_138) || '' } },
|
{ symbol: 'cUSDC_V2', name: 'Compliant USD cash eMoney (V2)', type: 'base', decimals: 6, currencyCode: 'USD', familySymbol: 'cUSDC', deploymentVersion: 'v2', deploymentStatus: 'staged', preferredForX402: true, liquiditySourceSymbol: 'cUSDC', description: 'Chain 138 x402 / permit-capable V2 deployment. Liquidity and PMM routing remain on cUSDC until cutover.', addresses: { [CHAIN_138]: addr('cUSDC_V2', CHAIN_138) || '' } },
|
||||||
// Chain 138 v0 only (no X): cUSDT on 138; cXUSDT used only for bridged/origin reference elsewhere. See ISO4217_COMPLIANT_TOKEN_MATRIX.md
|
// Chain 138 v0 only (no X): cUSDT on 138; cXUSDT used only for bridged/origin reference elsewhere. See ISO4217_COMPLIANT_TOKEN_MATRIX.md
|
||||||
{ symbol: 'cUSDT', name: 'Tether USD (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', v0Alias: 'cUSDT', addresses: { [CHAIN_138]: addr('cUSDT', CHAIN_138) || '', [CHAIN_651940]: addr('cUSDT', CHAIN_651940) || '', ...Object.fromEntries(L2_CHAIN_IDS.map((id) => [id, addr('cUSDT', id)])) } },
|
{ symbol: 'cUSDT', name: 'Compliant USD Treasury eMoney', type: 'base', decimals: 6, currencyCode: 'USD', v0Alias: 'cUSDT', addresses: { [CHAIN_138]: addr('cUSDT', CHAIN_138) || '', [CHAIN_651940]: addr('cUSDT', CHAIN_651940) || '', ...Object.fromEntries(L2_CHAIN_IDS.map((id) => [id, addr('cUSDT', id)])) } },
|
||||||
{ symbol: 'cUSDT_V2', name: 'Tether USD (Compliant V2)', type: 'base', decimals: 6, currencyCode: 'USD', familySymbol: 'cUSDT', deploymentVersion: 'v2', deploymentStatus: 'staged', preferredForX402: true, liquiditySourceSymbol: 'cUSDT', description: 'Chain 138 x402 / permit-capable V2 deployment. Liquidity and PMM routing remain on cUSDT until cutover.', addresses: { [CHAIN_138]: addr('cUSDT_V2', CHAIN_138) || '' } },
|
{ symbol: 'cUSDT_V2', name: 'Compliant USD Treasury eMoney (V2)', type: 'base', decimals: 6, currencyCode: 'USD', familySymbol: 'cUSDT', deploymentVersion: 'v2', deploymentStatus: 'staged', preferredForX402: true, liquiditySourceSymbol: 'cUSDT', description: 'Chain 138 x402 / permit-capable V2 deployment. Liquidity and PMM routing remain on cUSDT until cutover.', addresses: { [CHAIN_138]: addr('cUSDT_V2', CHAIN_138) || '' } },
|
||||||
{ symbol: 'cAUSDT', name: 'Alltra USD Token (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', description: 'Live Chain 138 compliant landing asset for the ALL Mainnet AUSDT corridor.', addresses: { [CHAIN_138]: addr('cAUSDT', CHAIN_138) || '' } },
|
{ symbol: 'cAUSDT', name: 'Alltra USD Token (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', description: 'Live Chain 138 compliant landing asset for the ALL Mainnet AUSDT corridor.', addresses: { [CHAIN_138]: addr('cAUSDT', CHAIN_138) || '' } },
|
||||||
{ symbol: 'cUSDW', name: 'USD W (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', description: 'Chain 138 repo-native cUSDW hub asset for D-WIN-aligned PMM and cWUSDW transport planning.', addresses: { [CHAIN_138]: addr('cUSDW', CHAIN_138) || '' } },
|
{ symbol: 'cUSDW', name: 'USD W (Compliant)', type: 'base', decimals: 6, currencyCode: 'USD', description: 'Chain 138 repo-native cUSDW hub asset for D-WIN-aligned PMM and cWUSDW transport planning.', addresses: { [CHAIN_138]: addr('cUSDW', CHAIN_138) || '' } },
|
||||||
{
|
{
|
||||||
@@ -595,8 +595,8 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
|
|||||||
},
|
},
|
||||||
// Public-network transport mirrors for canonical Chain 138 c* assets.
|
// Public-network transport mirrors for canonical Chain 138 c* assets.
|
||||||
{ symbol: 'cWAUSDT', name: 'Alltra USD Token (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form for the live Chain 138 cAUSDT surface.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWAUSDT', id)])) } },
|
{ symbol: 'cWAUSDT', name: 'Alltra USD Token (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form for the live Chain 138 cAUSDT surface.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWAUSDT', id)])) } },
|
||||||
{ symbol: 'cWUSDC', name: 'USD Coin (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDC', id)])) } },
|
{ symbol: 'cWUSDC', name: 'Wrapped cUSDC eMoney (GRU transport)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDC', id)])) } },
|
||||||
{ symbol: 'cWUSDT', name: 'Tether USD (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDT', id)])) } },
|
{ symbol: 'cWUSDT', name: 'Wrapped cUSDT eMoney (GRU transport)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDT', id)])) } },
|
||||||
{ symbol: 'cWEURC', name: 'Euro Coin (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'EUR', description: 'Public-network mirrored transport form of canonical Chain 138 cEURC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWEURC', id)])) } },
|
{ symbol: 'cWEURC', name: 'Euro Coin (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'EUR', description: 'Public-network mirrored transport form of canonical Chain 138 cEURC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWEURC', id)])) } },
|
||||||
{ symbol: 'cWEURT', name: 'Tether EUR (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'EUR', description: 'Public-network mirrored transport form of canonical Chain 138 cEURT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWEURT', id)])) } },
|
{ symbol: 'cWEURT', name: 'Tether EUR (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'EUR', description: 'Public-network mirrored transport form of canonical Chain 138 cEURT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWEURT', id)])) } },
|
||||||
{ symbol: 'cWGBPC', name: 'Pound Sterling (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'GBP', description: 'Public-network mirrored transport form of canonical Chain 138 cGBPC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWGBPC', id)])) } },
|
{ symbol: 'cWGBPC', name: 'Pound Sterling (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'GBP', description: 'Public-network mirrored transport form of canonical Chain 138 cGBPC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWGBPC', id)])) } },
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe('metamask-wallet-images', () => {
|
|||||||
expect(METAMASK_CHAIN138_ICON_URLS[0]).toMatch(/chain-138\.png$/);
|
expect(METAMASK_CHAIN138_ICON_URLS[0]).toMatch(/chain-138\.png$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves hub stable PNG logos', () => {
|
it('resolves hub eMoney PNG logos', () => {
|
||||||
expect(resolveMetamaskWalletImageUrl('cUSDC')).toMatch(/logo\.png$/);
|
expect(resolveMetamaskWalletImageUrl('cUSDC')).toMatch(/logo\.png$/);
|
||||||
expect(resolveMetamaskWalletImageUrl('cWUSDC')).toMatch(/logo\.png$/);
|
expect(resolveMetamaskWalletImageUrl('cWUSDC')).toMatch(/logo\.png$/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe('toWalletAddEthereumChainParams', () => {
|
|||||||
expect(out).toEqual({
|
expect(out).toEqual({
|
||||||
chainId: '0x8a',
|
chainId: '0x8a',
|
||||||
chainName: 'Defi Oracle Meta Mainnet',
|
chainName: 'Defi Oracle Meta Mainnet',
|
||||||
rpcUrls: ['https://rpc.public-0138.defi-oracle.io'],
|
rpcUrls: ['https://rpc-http-pub.d-bis.org'],
|
||||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||||
blockExplorerUrls: ['https://blockscout.defi-oracle.io'],
|
blockExplorerUrls: ['https://blockscout.defi-oracle.io'],
|
||||||
iconUrls: ['https://blockscout.defi-oracle.io/favicon.ico'],
|
iconUrls: ['https://blockscout.defi-oracle.io/favicon.ico'],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { estimateChain138DodoLiquidityUsd } from './chain138-dodo-liquidity';
|
import { estimateChain138DodoLiquidityUsd } from './chain138-dodo-liquidity';
|
||||||
|
|
||||||
describe('estimateChain138DodoLiquidityUsd', () => {
|
describe('estimateChain138DodoLiquidityUsd', () => {
|
||||||
it('values Chain 138 stable-to-stable DODO pools with token decimals', () => {
|
it('values Chain 138 fiat-value DODO pools with token decimals', () => {
|
||||||
const result = estimateChain138DodoLiquidityUsd({
|
const result = estimateChain138DodoLiquidityUsd({
|
||||||
token0Address: '0xf22258f57794CC8E06237084b353Ab30fFfa640b',
|
token0Address: '0xf22258f57794CC8E06237084b353Ab30fFfa640b',
|
||||||
token1Address: '0x71D6687F38b93CCad569Fa6352c876eea967201b',
|
token1Address: '0x71D6687F38b93CCad569Fa6352c876eea967201b',
|
||||||
@@ -36,9 +36,9 @@ describe('estimateChain138DodoLiquidityUsd', () => {
|
|||||||
reserve1: 24_900n * 10n ** 6n,
|
reserve1: 24_900n * 10n ** 6n,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.reserve0Usd).toBe(24_900);
|
expect(result.reserve0Usd).toBe(16_800);
|
||||||
expect(result.reserve1Usd).toBe(24_900);
|
expect(result.reserve1Usd).toBe(24_900);
|
||||||
expect(result.totalLiquidityUsd).toBe(49_800);
|
expect(result.totalLiquidityUsd).toBe(41_700);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('values non-USD canonical pairs from their repo-local peg references', () => {
|
it('values non-USD canonical pairs from their repo-local peg references', () => {
|
||||||
@@ -49,9 +49,9 @@ describe('estimateChain138DodoLiquidityUsd', () => {
|
|||||||
reserve1: 5n * 10n ** 6n,
|
reserve1: 5n * 10n ** 6n,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.reserve0Usd).toBe(24_900);
|
expect(result.reserve0Usd).toBe(16_800);
|
||||||
expect(result.reserve1Usd).toBeCloseTo(25_816.700630164178, 6);
|
expect(result.reserve1Usd).toBeCloseTo(25_816.700630164178, 6);
|
||||||
expect(result.totalLiquidityUsd).toBeCloseTo(50_716.70063016418, 6);
|
expect(result.totalLiquidityUsd).toBeCloseTo(42_616.70063016418, 6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('values XAU/stable DODO pools from the canonical gold peg', () => {
|
it('values XAU/stable DODO pools from the canonical gold peg', () => {
|
||||||
|
|||||||
@@ -230,6 +230,63 @@ contract AtomicX402IntentTest is Test {
|
|||||||
assertEq(cUSDCV2.balanceOf(address(sourceAdapter)), 0);
|
assertEq(cUSDCV2.balanceOf(address(sourceAdapter)), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testAsyncDestinationIntentRecordsPolygonProofBeforeSettlement() public {
|
||||||
|
bytes32 asyncCorridorId = coordinator.getCorridorId(
|
||||||
|
SOURCE_CHAIN, POLYGON_SELECTOR, address(cUSDCV2), address(0x7476a64B37732698b9f729fdB373a7B65e9677e9)
|
||||||
|
);
|
||||||
|
coordinator.configureCorridor(
|
||||||
|
AtomicTypes.CorridorConfig({
|
||||||
|
enabled: true,
|
||||||
|
degraded: false,
|
||||||
|
sourceChain: SOURCE_CHAIN,
|
||||||
|
destinationChain: POLYGON_SELECTOR,
|
||||||
|
assetIn: address(cUSDCV2),
|
||||||
|
assetOut: address(0x7476a64B37732698b9f729fdB373a7B65e9677e9),
|
||||||
|
maxNotional: 500_000e6,
|
||||||
|
maxReservedBps: 8_000,
|
||||||
|
targetBuffer: 0,
|
||||||
|
maxSettlementBacklog: 250_000e6,
|
||||||
|
maxOracleDriftBps: 500,
|
||||||
|
fulfilmentTimeout: 1 days,
|
||||||
|
settlementTimeout: 2 days,
|
||||||
|
defaultSettlementMode: SETTLEMENT_MODE
|
||||||
|
})
|
||||||
|
);
|
||||||
|
coordinator.setAsyncDestinationCorridor(asyncCorridorId, true);
|
||||||
|
registry.setCorridorAuthorization(fulfiller, asyncCorridorId, true);
|
||||||
|
|
||||||
|
AtomicX402SourceAdapter asyncSourceAdapter = new AtomicX402SourceAdapter(
|
||||||
|
address(legacyCUSDC),
|
||||||
|
address(cUSDCV2),
|
||||||
|
address(0x7476a64B37732698b9f729fdB373a7B65e9677e9),
|
||||||
|
address(coordinator),
|
||||||
|
SOURCE_CHAIN,
|
||||||
|
POLYGON_SELECTOR
|
||||||
|
);
|
||||||
|
coordinator.grantRole(coordinator.INTENT_ADAPTER_ROLE(), address(asyncSourceAdapter));
|
||||||
|
|
||||||
|
legacyCUSDC.mint(user, 1_100e6);
|
||||||
|
vm.startPrank(user);
|
||||||
|
legacyCUSDC.approve(address(asyncSourceAdapter), 1_100e6);
|
||||||
|
bytes32 obligationId = asyncSourceAdapter.createIntent(
|
||||||
|
address(legacyCUSDC), 1_100e6, 1_000e6, user, block.timestamp + 1 hours, asyncCorridorId
|
||||||
|
);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
AtomicTypes.CorridorLiquidityState memory state =
|
||||||
|
vault.getCorridorLiquidityState(asyncCorridorId, address(0x7476a64B37732698b9f729fdB373a7B65e9677e9));
|
||||||
|
assertEq(state.reservedLiquidity, 0);
|
||||||
|
|
||||||
|
vm.prank(fulfiller);
|
||||||
|
coordinator.submitAsyncDestinationFulfillment(
|
||||||
|
obligationId, SETTLEMENT_MODE, keccak256("polygon-tx"), keccak256("invoice")
|
||||||
|
);
|
||||||
|
|
||||||
|
AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
|
||||||
|
assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Fulfilled));
|
||||||
|
assertEq(obligation.fulfiller, fulfiller);
|
||||||
|
}
|
||||||
|
|
||||||
function testSettlementAdapterCallsCwBridgeLockAndSend() public {
|
function testSettlementAdapterCallsCwBridgeLockAndSend() public {
|
||||||
MockCWMultiTokenBridgeL1 bridge = new MockCWMultiTokenBridgeL1();
|
MockCWMultiTokenBridgeL1 bridge = new MockCWMultiTokenBridgeL1();
|
||||||
bridge.setFee(0.01 ether);
|
bridge.setFee(0.01 ether);
|
||||||
|
|||||||
Reference in New Issue
Block a user