Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions crates/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1954,15 +1954,14 @@ Fund the payment wallet out of band — the testnet faucet below, or by sending
`payment_address()` directly. Credits are provisioned against the account gateway-side.

EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
signature encoded as Base58. Solana wallets must be funded out of band; the faucet
is available for Base Sepolia only.
signature encoded as Base58.

| Method | Cost | Returns |
|---|---|---|
| `payment_address()` | free, offline | the wallet address derived from the key |
| `gateway_authenticate()` | free | `GatewaySession { token, exp_unix, account_id }` |
| `gateway_credits(session)` | free | `CreditBalance { account_id, credits }` |
| `gateway_drip(session)` | free (testnet) | `DripReceipt { account_id, transaction_hash }` |
| `gateway_drip(session)` | free (testnet) | `DripReceipt` with `transfer_id` or `transaction_hash` |
| `gateway_drawdown_call(method, params, network, session)` | 1 credit | the JSON-RPC `result` |

```rust
Expand All @@ -1977,9 +1976,10 @@ retry that call.

#### Testnet faucet

`gateway_drip` requests testnet tokens for the payment **wallet** on Base Sepolia. The
gateway allows one drip per account, and it returns the on-chain funding transaction hash
— not a credit balance.
`gateway_drip` requests testnet tokens for the payment **wallet**. Circle Gateway-backed
networks return `transfer_id` because settlement is asynchronous. Direct-transfer
networks such as Arc Testnet return `transaction_hash`. The response is not a credit
balance; call `gateway_credits` separately.

### MPP payment channel (deposit once, then vouchers)

Expand Down
64 changes: 54 additions & 10 deletions crates/core/src/rpc/payment/drawdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,19 +233,22 @@ pub async fn credits(
})
}

/// The faucet drip result: the on-chain funding transaction. The gateway's
/// `/drip` returns the settlement tx, not a credit balance — call [`credits`]
/// afterwards to read the updated balance.
/// The faucet drip result. Circle Gateway-backed networks return a transfer ID
/// because settlement is asynchronous. Direct-transfer networks return the
/// transaction hash instead.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DripReceipt {
pub account_id: String,
/// The faucet funding transaction hash.
pub transaction_hash: String,
pub wallet_address: Option<String>,
pub network: Option<String>,
pub transfer_id: Option<String>,
pub amount_usdc: Option<String>,
pub transaction_hash: Option<String>,
}

/// Requests testnet tokens from the faucet (POST `/drip`, Bearer JWT). The
/// gateway allows this once per account on Base Sepolia and returns the funding
/// transaction (NOT a balance). Solana wallets must be funded out of band.
/// gateway allows this once per wallet and network per month. The response is
/// not a balance; call [`credits`] afterwards to read the credit balance.
pub async fn drip(
client: &reqwest::Client,
payment: &ResolvedPayment,
Expand All @@ -268,13 +271,24 @@ pub async fn drip(
struct DripBody {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "walletAddress")]
wallet_address: Option<String>,
network: Option<String>,
#[serde(rename = "transferId")]
transfer_id: Option<String>,
#[serde(rename = "amountUsdc")]
amount_usdc: Option<String>,
#[serde(rename = "transactionHash")]
transaction_hash: String,
transaction_hash: Option<String>,
}
let parsed: DripBody =
serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })?;
Ok(DripReceipt {
account_id: parsed.account_id,
wallet_address: parsed.wallet_address,
network: parsed.network,
transfer_id: parsed.transfer_id,
amount_usdc: parsed.amount_usdc,
transaction_hash: parsed.transaction_hash,
})
}
Expand Down Expand Up @@ -850,7 +864,7 @@ mod tests {
}

#[tokio::test]
async fn drip_returns_the_funding_transaction() {
async fn drip_returns_a_direct_transfer_transaction() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/drip"))
Expand All @@ -870,10 +884,40 @@ mod tests {
};
let client = reqwest::Client::new();
let receipt = drip(&client, &payment, &session).await.unwrap();
assert_eq!(receipt.transaction_hash, "0xfeed");
assert_eq!(receipt.transaction_hash.as_deref(), Some("0xfeed"));
assert_eq!(receipt.transfer_id, None);
assert_eq!(receipt.account_id, "eip155:84532:0xabc");
}

#[tokio::test]
async fn drip_returns_a_circle_transfer_id() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/drip"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"accountId": "eip155:84532:0xabc",
"walletAddress": "0xabc",
"network": "eip155:84532",
"transferId": "transfer-1",
"amountUsdc": "10"
})))
.mount(&server)
.await;

let payment = evm_payment(&server.uri());
let session = GatewaySession {
token: "jwt-abc".into(),
exp_unix: now_unix() as i64 + 3600,
account_id: "a".into(),
};
let receipt = drip(&reqwest::Client::new(), &payment, &session)
.await
.unwrap();
assert_eq!(receipt.transfer_id.as_deref(), Some("transfer-1"));
assert_eq!(receipt.amount_usdc.as_deref(), Some("10"));
assert_eq!(receipt.transaction_hash, None);
}

// Menu with credit, per-request, and nanopayment tiers.
fn gateway_menu() -> Value {
let mut nanopayment = x402_credit_offer("100")
Expand Down
9 changes: 7 additions & 2 deletions crates/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1655,8 +1655,9 @@ impl RpcApiClient {
}

/// Requests testnet tokens from the x402 faucet. Resolves to the funding
/// transaction `{ accountId, transactionHash }` — NOT a balance; call
/// `gatewayCredits` afterwards for that. Allowed once per account.
/// transfer metadata containing either `transferId` or `transactionHash`.
/// The former is asynchronous Circle settlement; the latter is a direct
/// on-chain transfer. This is not a balance; call `gatewayCredits` for that.
#[napi]
pub async fn gateway_drip(&self, session: serde_json::Value) -> Result<serde_json::Value> {
let session = parse_gateway_session(&session)?;
Expand All @@ -1667,6 +1668,10 @@ impl RpcApiClient {
.map_err(errors::map_sdk_err)?;
Ok(serde_json::json!({
"accountId": receipt.account_id,
"walletAddress": receipt.wallet_address,
"network": receipt.network,
"transferId": receipt.transfer_id,
"amountUsdc": receipt.amount_usdc,
"transactionHash": receipt.transaction_hash,
}))
}
Expand Down
8 changes: 6 additions & 2 deletions crates/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2712,8 +2712,8 @@ impl RpcApiClient {
}

/// Requests testnet tokens from the x402 faucet. Returns the funding
/// transaction as a dict `{account_id, transaction_hash}` — NOT a balance;
/// call `gateway_credits` afterwards for that. Allowed once per account.
/// transfer metadata as a dict. Circle-backed responses contain
/// `transfer_id`; direct transfers contain `transaction_hash`.
#[gen_stub(override_return_type(
type_repr = "typing.Coroutine[typing.Any, typing.Any, typing.Any]"
))]
Expand All @@ -2731,6 +2731,10 @@ impl RpcApiClient {
.map_err(errors::map_sdk_err)?;
json_to_py(&serde_json::json!({
"account_id": receipt.account_id,
"wallet_address": receipt.wallet_address,
"network": receipt.network,
"transfer_id": receipt.transfer_id,
"amount_usdc": receipt.amount_usdc,
"transaction_hash": receipt.transaction_hash,
}))
})
Expand Down
6 changes: 5 additions & 1 deletion crates/ruby/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2051,7 +2051,7 @@ impl RpcApiClient {
}))
}

// gateway_drip(session:) — request testnet funds; returns the tx hash.
// gateway_drip(session:) — request testnet funds; returns transfer metadata.
fn gateway_drip(&self, opts: RHash) -> Result<magnus::Value, Error> {
validate_keys(&opts, &["session"])?;
let session = require_gateway_session(&opts)?;
Expand All @@ -2061,6 +2061,10 @@ impl RpcApiClient {
.map_err(map_err)?;
to_ruby(serde_json::json!({
"account_id": receipt.account_id,
"wallet_address": receipt.wallet_address,
"network": receipt.network,
"transfer_id": receipt.transfer_id,
"amount_usdc": receipt.amount_usdc,
"transaction_hash": receipt.transaction_hash,
}))
}
Expand Down
12 changes: 6 additions & 6 deletions npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1838,15 +1838,14 @@ Fund the payment wallet out of band — the testnet faucet below, or by sending
`paymentAddress()` directly. Credits are provisioned against the account gateway-side.

EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
signature encoded as Base58. Solana wallets must be funded out of band; the faucet
is available for Base Sepolia only.
signature encoded as Base58.

| Method | Cost | Returns |
|---|---|---|
| `paymentAddress()` | free, offline | the wallet address derived from the key |
| `gatewayAuthenticate()` | free | `GatewaySession { token, expUnix, accountId }` |
| `gatewayCredits(session)` | free | `CreditBalance { accountId, credits }` |
| `gatewayDrip(session)` | free (testnet) | `DripReceipt { accountId, transactionHash }` |
| `gatewayDrip(session)` | free (testnet) | `DripReceipt` with `transferId` or `transactionHash` |
| `gatewayDrawdownCall(method, session, network, params?)` | 1 credit | the JSON-RPC `result` |

```typescript
Expand All @@ -1861,9 +1860,10 @@ that call.

#### Testnet faucet

`gatewayDrip` requests testnet tokens for the payment **wallet** on Base Sepolia. The
gateway allows one drip per account, and it returns the on-chain funding transaction hash
— not a credit balance.
`gatewayDrip` requests testnet tokens for the payment **wallet**. Circle Gateway-backed
networks return `transferId` because settlement is asynchronous. Direct-transfer
networks such as Arc Testnet return `transactionHash`. The response is not a credit
balance; call `gatewayCredits` separately.

### MPP payment channel (deposit once, then vouchers)

Expand Down
12 changes: 6 additions & 6 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1831,15 +1831,14 @@ Fund the payment wallet out of band — the testnet faucet below, or by sending
`payment_address()` directly. Credits are provisioned against the account gateway-side.

EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
signature encoded as Base58. Solana wallets must be funded out of band; the faucet
is available for Base Sepolia only.
signature encoded as Base58.

| Method | Cost | Returns |
|---|---|---|
| `payment_address()` | free, offline | the wallet address derived from the key |
| `gateway_authenticate()` | free | a dict `{token, exp_unix, account_id}` |
| `gateway_credits(session)` | free | a dict `{account_id, credits}` |
| `gateway_drip(session)` | free (testnet) | a dict `{account_id, transaction_hash}` |
| `gateway_drip(session)` | free (testnet) | a dict with `transfer_id` or `transaction_hash` |
| `gateway_drawdown_call(method, session, network, params=None)` | 1 credit | the JSON-RPC `result` |

```python
Expand All @@ -1854,9 +1853,10 @@ that call.

#### Testnet faucet

`gateway_drip` requests testnet tokens for the payment **wallet** on Base Sepolia. The
gateway allows one drip per account, and it returns the on-chain funding transaction hash
— not a credit balance.
`gateway_drip` requests testnet tokens for the payment **wallet**. Circle Gateway-backed
networks return `transfer_id` because settlement is asynchronous. Direct-transfer
networks such as Arc Testnet return `transaction_hash`. The response is not a credit
balance; call `gateway_credits` separately.

### MPP payment channel (deposit once, then vouchers)

Expand Down
12 changes: 6 additions & 6 deletions ruby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1839,15 +1839,14 @@ Fund the payment wallet out of band — the testnet faucet below, or by sending
`payment_address` directly. Credits are provisioned against the account gateway-side.

EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
signature encoded as Base58. Solana wallets must be funded out of band; the faucet
is available for Base Sepolia only.
signature encoded as Base58.

| Method | Cost | Returns |
|---|---|---|
| `payment_address` | free, offline | the wallet address derived from the key |
| `gateway_authenticate` | free | a Hash `{token:, exp_unix:, account_id:}` |
| `gateway_credits(session:)` | free | a Hash `{account_id:, credits:}` |
| `gateway_drip(session:)` | free (testnet) | a Hash `{account_id:, transaction_hash:}` |
| `gateway_drip(session:)` | free (testnet) | a Hash with `transfer_id:` or `transaction_hash:` |
| `gateway_drawdown_call(method:, session:, network:, params:)` | 1 credit | the JSON-RPC `result` |

```ruby
Expand All @@ -1864,9 +1863,10 @@ that call.

#### Testnet faucet

`gateway_drip` requests testnet tokens for the payment **wallet** on Base Sepolia. The
gateway allows one drip per account, and it returns the on-chain funding transaction hash
— not a credit balance.
`gateway_drip` requests testnet tokens for the payment **wallet**. Circle Gateway-backed
networks return `transfer_id` because settlement is asynchronous. Direct-transfer
networks such as Arc Testnet return `transaction_hash`. The response is not a credit
balance; call `gateway_credits` separately.

### MPP payment channel (deposit once, then vouchers)

Expand Down
Loading